3

我有一个以对象类型作为返回值类型的成员函数:

MyObject myfunction(parameters) {
    if (some condition) { 
        return MyObject(parameters);
    } else { 
        ... no valid object can be created ... 
    } 
}

在某些情况下(在函数体中检查),无法创建和返回 MyObject 类型的对象。

作为一个偶尔的 c++ 程序员,我可以自发地提出三个解决方案:

  1. 将返回值类型更改为 * MyObject 并在无法创建有效对象时返回 nullptr (C++11),然后在调用代码中检查与 nullptr 的相等性。
  2. 如果无法创建对象并在调用代码中捕获该对象,则抛出异常。
  3. 使用我定义为无效的一些值创建一个对象,并在使用返回的对象之前检查它。

处理这种情况的标准方法和性能方面的最佳解决方案是什么?...或一些我看不到的明显解决方法...

最先进的 C++11 解决方案将是完美的 :-)

到目前为止我的想法:
解决方案 1 似乎还可以,但仅适用于 C++11,我必须在堆上创建返回的对象才能将其传递给主程序(将对象本身返回给调用函数,因此将其保留在堆栈中对于小对象可能会更快?)。
解决方案 2 可能会更慢,并导致主程序中的编码冗长。
解决方案 3 可能是最慢的(徒劳地创建了一个对象)并且在主程序中检查不是很方便。

对于我的代码,没有有效的返回对象是默认情况而不是异常,并且创建的对象相当小,但是考虑不同情况的一般考虑对于其他读者的应用程序肯定有用......

非常感谢大家的帮助:-)

4

4 回答 4

7

在通常情况下,返回Boost.Optional有效:

boost::optional<MyObject> myfunction(parameters) {
    if (some condition) { 
        return MyObject(parameters);
    } else { 
        return boost::none;
    } 
}

在通话现场:

auto ret = myfunction(...);
if(ret)
  // use '*ret'  or 'ret.get()'

但正如 R. Martinho 所提到的,这个解决方案有一些缺点(即,仅移动类型不起作用,因为 Boost.Optional 尚未更新以支持移动语义)。

于 2012-10-26T14:17:56.803 回答
4

由于您的问题是笼统的,我也将笼统地回答。

如果你有一个函数,它的工作是创建和返回一个对象,那么就是它的工作。

现在,如果您想以这样一种方式设计此函数,以便在不满足构建对象所需的某些条件时返回该对象,您实际上已经更改了此函数的语义。现在,它只承担一项职责,它具有三个职责:

  1. 确定是否存在构建对象的正确条件
  2. 如果是,则构造并返回对象
  3. 如果否,则不返回任何内容,或指示未创建的某些条件值

单一职责原则”表明,一般来说,好的设计规定一个功能(或类或你有什么)应该有一项工作要做。在这里,您的功能有三个。

我建议您建议的方法一般都不是最好的。相反,我会选择:

4:实现一个单独的函数来确定构造对象的资格。如果该函数返回 true,则调用 myFunction构造对象并返回它。

于 2012-10-26T14:23:19.663 回答
4

根据具体情况,您建议的所有 3 个解决方案都是有效且常见的。

如果无法创建对象是可能导致调用函数不得不中止、备份和重试或采取其他极端措施的错误条件,则抛出异常。

如果无法创建对象是例行事件,并且您希望调用者检查对象是否已创建并在任何一种情况下都正常进行,则返回 null 是一个很好的解决方案。

如果可以创建一个合理的虚拟或空白对象,那是一个很好的解决方案。但这非常罕见。仅当调用者将实际处理虚拟对象时,您才应该这样做。

如果你返回一个空指针,然后你发现你调用这个函数的每个地方都在写

MyObject* myobject=myfunction(whatever);
if (myobject==null) throw new PanicException;

那么你不妨在函数内部抛出异常。

更糟糕的是,如果您正在编写:

MyObject* myobject=myfunction(whatever);
if (myobject!=null)
{
  ... process it ...
}
else
{
   ... display error message ...
}

然后,您只是使用 IF 语句模拟异常处理。使用真正的例外。

另一方面,如果你抛出一个异常,然后你发现你经常写:

MyObject* myobject;
try
{
  myobject=myfunction(whatever);
}
catch (PanicException pe)
{
  myobject=null;
}

那么,你最好只返回空值。

我偶尔会创建虚拟对象。最常见的情况是当一个函数返回一个集合(如数组或链表)时,如果我找不到要放入集合中的数据,则返回一个包含零元素的集合。然后调用者循环遍历集合中的元素,如果没有,那很好。我遇到过一些情况,其中我返回了一个带有零长度字符串的对象,用于名称或客户 ID 或其他任何内容。但总的来说,如果您只是返回一个虚拟对象,以便调用者可以测试并说,哦,这是一个虚拟对象,然后将其丢弃,我认为您最好返回 null。

顺便说一句,当您说只能在 C++11 中返回空指针时,不确定您的意思。传递空值的能力可以追溯到我见过的最早的 C++ 版本。

于 2012-10-26T14:31:44.880 回答
2

除非阻止创建对象的条件异常,否则应使用第一种解决方案。否则返回 NULL 指针是一个完全有效的解决方案......即使在 C++11 中也是如此。

“指针”当然应该使用 RAII 容器(例如 std::unique_ptr)进行包装。真的应该是你代码的常见做法。

如果您问我,第三种解决方案是完全浪费资源。您将不得不创建一个无效(无用)的对象,并将其复制为返回值......只是为了将其丢弃。

于 2012-10-26T14:17:44.103 回答