代码之家  ›  专栏  ›  技术社区  ›  Konrad

从返回引用的函数提前返回的最佳方法

  •  8
  • Konrad  · 技术社区  · 15 年前

    假设我们有一个形式的函数:

    const SomeObject& SomeScope::ReturnOurObject()
    {
        if( ! SomeCondition )
        {
            // return early
            return  ;
        }
    
        return ourObject;
    }
    

    显然,上面的代码有一个问题,如果条件失败,那么我们就有一个关于如何从这个函数返回的问题。 我问题的关键是,处理这种情况的最佳方法是什么?

    14 回复  |  直到 13 年前
        1
  •  24
  •   sbi    15 年前

    这不是一个句法问题,而是一个设计问题。你必须指定 ReturnOurObject() 应该在什么时候回来 SomeCondition 是真的。这主要取决于函数的用途。你还没告诉我们。

    根据设计问题,我看到了一些可能的句法方法:

    • 返回对其他某个对象的引用;您必须在某个位置具有一些ersatz对象
    • 在您返回引用的某个地方有一个特殊的“无需返回的对象”;客户机可以检查它;如果不检查,则会得到合理的默认行为。
    • 返回指针,而不是引用;客户机必须始终检查函数的返回值
    • 引发异常;如果 静止状态 客户无法处理的特殊情况是否合适?
    • 断言;如果 静止状态 应该始终保持,应该断言
        2
  •  13
  •   Adrian Grigore    15 年前

    在这种情况下,我将使用指针而不是引用。实际上,这个标准(可选或强制返回值)首先是我如何决定指针和引用之间的关系。

        3
  •  6
  •   Steve Jessop    15 年前

    我可能会这样做:

    const SomeObject& SomeScope::ReturnOurObject()
    {
        if( ! SomeCondition )
        {
            throw SomeException();
        }
        return ourObject;
    }
    
    const SomeObject *SomeScope::ReturnOurObjectIfPermitted()
    {
        return SomeCondition ? &ourObject : 0;
    }
    

    或许还有:

    bool SomeScope::CheckMode();
        return SomeCondition;
    }
    

    然后,呼叫者有一些选项:

    // 1 - "I know that I'm in the right mode"
    myScope.ReturnOurObject().DoSomething();
    
    // 2 - "I don't know whether I'm in the right mode, but I can cope either way"
    if (SomeObject *myObject = myScope.ReturnOurObjectIfPermitted()) {
        myObject->DoSomething();
    } else {
        DoSomethingElse();
    }
    
    // 2 - alternate version:
    if (myScope.CheckMode()) {
        SomeObject &myObject = myScope.ReturnOurObject();
        myObject.DoSomething();
    } else {
        DoSomethingElse();
    }
    
    // 3 - "I don't know whether I'm in the right mode. If I'm not then
    // I can't deal with it now, but some higher-level code can"
    try {
        // ... several calls deep ...
        myScope.ReturnOurObject().DoSomething();
        // ... several returns back ...
    } catch (SomeException &e) {
        DoSomethingElse();
    }
    

    根据先决条件(即确保函数能够完成其工作的职责),可以对函数的接口进行不同的设计。

    如果这是呼叫者的责任,那么他们需要有能力确保这一点。不管怎样,为了安全起见,你可能想检查一下。当所谓的“先决条件”不满足时抛出异常会使它们更像是建议而不是条件,而且如果您不介意在任何地方检查的运行时开销,它可以很好地工作。我通常对前提条件相当不宽容,所以实际上我可以用断言替换条件异常,并记录不能在错误的模式下调用函数。这取决于调用者对模式的控制程度——很明显,如果它是任意改变的(例如,如果“somecondition”是“UART上没有可用的字节”),那么您需要一个非常不同的接口,如果它只是在客户机代码调用某个函数来改变它时才改变的话。

    如果调用者没有责任使模式正确,那么您的接口不应该写您的实现不能兑现的支票。在这种情况下,如果“预期”没有要返回的对象,则返回不应通过引用。除非(a)你能发出一个特殊的“对不起,它不起作用”返回对象,呼叫者可以检查,或(b)你可以在“预期”的情况下抛出异常。IMO(B)在C++中是很少见的。(A)通常不会比返回空指针更好,但偶尔也会这样。例如,如果返回一个空集合以表示“这里不允许您看到任何内容”,则可能会导致一些非常干净的调用代码,如果 DoSomethingElse() 本来是“什么都不做”。因此,除了期望的职责之外,接口还取决于预期的用例。

        4
  •  5
  •   Yacoby    15 年前

    如果不想将返回类型更改为指针,则可以使用 null object pattern

        5
  •  5
  •   Timo Geusch    15 年前

    “我们的对象”的生命周期是什么?它是在哪里创建的?

    如果您需要提前返回而不接触/更新我们的对象,并且它存在于您要返回的点上,那么我认为返回对它的引用没有问题。

    如果您试图传达出发生了错误,那么您可能需要抛出异常而不是提前返回,因为您的函数同意的契约说它将返回对“someobject”的引用。

    如果要返回临时引用,最好修复 那个 问题…

        6
  •  5
  •   Patrice Bernassola    15 年前

    我将返回一个布尔值,并将对象引用作为参数传递给函数:

    bool getObject(SomeObject& object)
    {
        if( condition )
             return false;
    
        object = ourObject;
        return true;
    }
    

    在这种情况下,只有当函数返回true时,对象才会被填充。

        7
  •  3
  •   Tyson    15 年前

    休斯敦大学。。。只是抛出一个例外…

        8
  •  3
  •   denisenkom    15 年前

    引发异常

        9
  •  1
  •   Naveen    15 年前

    只有当我确信函数永远不会失败时,我才会考虑通过引用返回(可能类似于向内部成员变量返回常量引用)。如果函数内部有多个验证,那么我将考虑以下选项之一:

    • 如果复制对象不是 昂贵,退回一份
    • 如果复制成本很高,请返回 const指针。在以下情况下返回空值 失败。
        10
  •  1
  •   AshleysBrain    15 年前

    如果不想抛出异常,可以尝试如下操作:

    const SomeObject& GetSomeObject(const SomeObject& default_if_not_found = SomeObject())
    {
        // ...
        if (!found)
            return default_if_not_found;
    
        return ourObject;
    }
    

    这意味着您不必在某个地方放置某种类型的静态变量来返回,而且您仍然可以在没有参数的情况下调用它。我猜您会假设默认的constructed someobject()状态是空状态,这样您就可以测试某个对象,看它是否处于空状态。有时使用它来重写默认返回值也很有用,例如getsomeobject(someobject(“I'm empty”))

        11
  •  1
  •   DigitalRoss    15 年前

    正好有三个选择…

    1。 引发异常
    2。 返回一个虚拟错误对象
    三。 使用指针并返回空值

        12
  •  0
  •   user213741    15 年前

    我相信答案取决于你是否是那种认为异常是坏的,从不想使用它们的程序员。如果确实使用了它们,而且这确实是一个异常条件,那么必须抛出异常。是的,用户需要处理它,但是隐藏一个错误来假装什么都没有发生是不可能的。 但如果从不使用异常,那么只需返回一个指针或布尔值,并向函数中添加对某个对象的非常量引用。 这两种解决方案都很简单,但谁说过度复杂的事情会使代码变得更好?

        13
  •  0
  •   David Thornley    15 年前

    函数的设置方式必须返回 SomeObject 或者抛出异常。如果您想做其他事情,您需要更改函数。没有其他选择。

    如果 SomeCondition 永远不会是错误的,然后您可以删除代码中的测试并将 assert 或者您可以保留测试并抛出异常。(我不愿意建议忽略这种可能性,因为“从不假”几乎总是意味着“除非发生了不好的事情,而且没有被发现”,如果它值得在任何条件下进行测试,那么它就值得被发现。)

    有可能返回某种空对象。指针非常有用,因为有一个明显的空值测试为false,但是您可以将标志作为out参数传递回去,或者返回一个特殊的对象作为空值。然后您需要测试每个调用的返回值,以使其有意义,而且这不可能比 断言 throw .

    如果我这样做的话,这取决于 静止状态 . 如果它有副作用或者不难计算,测试并抛出一个错误的异常。如果计算困难,使用 断言 所以你不必在生产中去做,除此之外别忘了它。

        14
  •  0
  •   newacct    13 年前

    这与内置C++运算符的情况相同。 dynamic_cast<> . 它可以强制转换为指针类型或引用类型。当它是指针类型时,它返回 NULL 失败论。当它是引用类型时,它抛出异常 std::bad_cast 失败论。在同样的例子之后,您将抛出一个异常。