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

时间:2009-11-18 10:22:44

标签: c++ function pass-by-reference

让我们说我们有一个形式的功能:

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

    return ourObject;
}

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

14 个答案:

答案 0 :(得分:24)

这不是语法问题,而是设计问题。当ReturnOurObject()为真时,您必须指定SomeCondition应返回的内容。这主要取决于该功能将用于什么。你还没有告诉我们。

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

  • 返回对其他对象的引用;你必须在某个地方有一些ersatz对象
  • 在某处返回引用的特殊“无对象返回”对象;客户可以检查这个;如果他们不检查他们得到合理的默认行为
  • 返回指针,而不是引用;客户端必须始终检查函数的返回值
  • 抛出异常;如果SomeCondition是特殊的,客户无法处理这种情况是合适的
  • 断言;如果SomeCondition应该始终保持,则应该断言

答案 1 :(得分:13)

在这种情况下,我会使用指针而不是引用。事实上,这个标准(可选或强制返回值)是我在指针和引用之间的决定。

答案 2 :(得分:6)

我可能会这样做:

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()将“无所事事”,那么返回一个空集合意味着“没有任何你可以在这里看到的”可能会产生一些非常干净的调用代码。因此,除了期望的职责外,界面还取决于预期的用例。

答案 3 :(得分:5)

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

答案 4 :(得分:5)

'ourObject'的生命周期是什么?它在哪里创建?

如果您需要提前返回而不触及/更新我们的对象,并且它存在于您返回的位置,我看不到返回对它的引用的问题。

如果您正在尝试通知存在错误,则可能必须抛出异常而不是提前返回,因为您的函数同意的合同表明它将返回对'SomeObject'的引用。 / p>

如果您要返回对临时的引用,最好修复 问题......

答案 5 :(得分:5)

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

bool getObject(SomeObject& object)
{
    if( condition )
         return false;

    object = ourObject;
    return true;
}

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

答案 6 :(得分:3)

呃......只是抛出异常...

答案 7 :(得分:3)

抛出异常

答案 8 :(得分:1)

只有当我确定函数永远不会失败时,我才会考虑通过引用返回(可能就像将const-reference返回给内部成员变量一样)。如果函数内部有多个验证,那么我将考虑以下选项之一:

  • 如果复制对象不是 昂贵的,返回一份副本
  • 如果复制成本高,请返回 const的指针。如果是,则返回NULL 故障。

答案 9 :(得分:1)

如果您不想抛出异常,可以尝试以下方法:

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

    return ourObject;
}

这意味着您不必在某处返回某种静态变量,您仍然可以在没有参数的情况下调用它。我猜你假设默认构造的SomeObject()状态是空状态,所以你可以测试SomeObject以查看它是否处于空状态。使用它来覆盖默认返回值有时也很有用,例如。 GetSomeObject(SomeObject(“我是空的”))

答案 10 :(得分:1)

正好三个选择......

1。 抛出异常
2. 返回虚拟错误对象
3. 使用指针并返回NULL

答案 11 :(得分:0)

我相信答案取决于你是否是那种认为异常是坏的并且从不想使用它们的程序员。如果您确实使用它们,并且这确实是一个例外情况,那么必须抛出异常。是的,用户需要处理它,但隐藏错误以假装没有发生任何事情根本不是一个选择。 但是如果你从不使用异常,那么只需返回一个指针或一个布尔值,并向你的函数添加一个SomeObject的非const引用。 这两种解决方案都很简单,但谁说过度复杂的事情使代码更好?

答案 12 :(得分:0)

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

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

有可能返回某种null对象。指针对此很有用,因为有一个明显的空值可以测试false,但是你可以将一个标志作为out参数传回或者返回一个打算作为null的特殊对象。那么你将需要测试每次调用的返回值,以使其有意义,并且这不会比assertthrow更轻松。

如果我这样做,那将取决于SomeCondition。如果它有副作用或不难计算,如果错误则测试并抛出异常。如果计算繁琐,请使用assert,这样您就不必在生产中执行此操作,除此之外不要忘记它。

答案 13 :(得分:0)

这与内置C ++运算符dynamic_cast<>的情况相同。它可以转换为指针类型或引用类型。当它是指针类型时,它会在失败时返回NULL。当它是引用类型时,它会在失败时抛出异常std::bad_cast。按照相同的示例,您将抛出异常。