什么时候使用 boost::optional,什么时候使用 std::unique_ptr,在需要实现一个可以返回“无”值的函数时?

24

据我所了解,您可以有两种方式实现一个函数,有时不返回结果(例如在人员列表中找不到某个人)。

*- 我们忽略裸指针版本,可以使用带有布尔标志的pair或抛出异常的版本。

boost::optional<Person> findPersonInList();
或者
std::unique_ptr<Person> findPersonInList();

那么有没有理由更喜欢其中一个而不是另一个呢?


6
“*- 我们忽略原始指针版本,使用带有布尔标志的pair。” -> 这是什么意思?…一个 unique_ptr 对于查找操作非常不合适,你需要的正是一个原始指针,而布尔标志是完全不必要的! - Andrei Tita
3
简洁翻译:始终使用boost::optional - Konrad Rudolph
8
为什么牵涉到所有权? - R. Martinho Fernandes
@R.MartinhoFernandes:这也让我感到困扰;返回句柄和副本之间有很大的区别。 - Matthieu M.
2
@R.MartinhoFernandes 因为我不是一个好的程序员。但是在SO上很好的人回答了我的问题,所以今天我学到了一些东西。 :) - NoSenseEtAl
显示剩余2条评论
7个回答

27
这取决于您希望返回一个“句柄”还是一个“副本”。
如果您希望返回一个“句柄”,则以下两个选择都可以:
- Person* - boost::optional<Person&>
我倾向于使用一个Ptr<Person>类,如果出现空访问就会抛出异常,但这是我的偏执狂。
如果您希望返回一个“副本”,则对于非多态类,可以使用boost::optional<Person>,对于多态类,需要使用std::unique_ptr<Person>,因为动态分配会产生开销,因此只在必要时使用它。

4
不使用指针就不必在空访问时抛出异常(你几乎永远不应该有空指针)。 - Cat Plus Plus
4
你更倾向于抛出NotFound而不是NullPointer吗?它们有什么区别? - Matthieu M.
5
回复你的评论:NotFound 很明显更受青睐,因为它更直接地表达了语义。然而,在这个上下文中抛出异常也不是一个好的选择。 - Konrad Rudolph
1
@KonradRudolph:我倾向于使用空指针替代方案,因为这样客户端就可以自行决定是显式检查还是依赖异常。如果客户端期望可能为空值,则最好显式检查(使用早期返回的 if 比嵌套 try/catch 更简洁),如果他们不期望它(这次没有意义),他们仍然受到保护。 - Matthieu M.
1
@R.MartinhoFernandes:该死!:p - Matthieu M.
显示剩余10条评论

14

通常情况下,您的意图应该使用boost::optional表达而不是std::unique_ptr。尽管如此,特殊情况下的find操作可能应该遵循标准库的方式来实现,假定您底层类型具有迭代器的概念:如果找不到元素,则返回指向end()的迭代器;否则返回指向该元素的迭代器。


8

boost::optional 更清晰地表明了您的意图。您需要明确记录空的 std::unique_ptr 表示没有值可返回。


4
我不同意,空指针(无论是原始的还是智能的)已经 几十年 表示结果缺失了,它很普遍。 - Matthieu M.
4
@MatthieuM:可能我解释得不清楚。如果一个函数返回一个std::unique_ptr,它可能没有返回值,并不明显,因为相似的总是返回值的函数将具有相同的签名。 - Andriy Tylychko
那我们只能各持己见了,对我来说指针总是和可能的空值相呼应...但我很多疑。 - Matthieu M.
3
“更清晰地表达您的意图”是明确的。 boost :: optional目的就是表达这个特定意图。因此,基本上,它更清晰,按定义而言。在这里没有什么好反对的,这是一个事实性的论点。此外,您忽略了unique_ptr的内在语义:它表示(独占)所有权 - Konrad Rudolph
3
我同意boost::optional能更清晰地表达意图,但如果您查看我的答案,您将会发现它存在一些潜在问题。它的本意是存储一个值,而不是动态分配的类,因此不适用于拥有多态类实例的情况。那时,您应该使用std::unique_ptr,在其上包装boost::optional来表达它可能为空是没有意义的。 - Matthieu M.
4
如你所说,空指针已经用来表示无结果的情况几十年了 - 但这并不意味着这是个好主意:链接 Tony Hoare / 历史上糟糕的想法:“空引用:价值十亿美元的错误” 摘要:我称之为我的十亿美元错误。它是在1965年发明了空引用。当时,我正在设计第一个完整的引用类型系统,用于一种面向对象语言(ALGOL W)…… - Paul Delhanty

6
有第四种方法可以实现:如果未找到任何内容,则使函数抛出异常。
我知道这并没有真正回答你的问题,因此我很抱歉,但也许你还没有考虑过这个方法。

嗨,我本来打算在 * :) 中添加这个功能,但还是谢谢。基本上,我不喜欢使用 ex 作为消息机制。 :D - NoSenseEtAl
1
如果集合中可能没有你要查找的元素,那么异常绝对不适用。我可以想象很快就会寻找不到我要找的不同短语;再加上异常开销,看起来就像一场灾难。 - Bartek Banachewicz
3
对我来说,异常处理的开销不在运行时,而是在源代码中。try-catch引入了两个单独的作用域,您需要进行一些技巧才能从中获得任何东西(即,您需要在try-catch之外声明一个变量,以便稍后使用它;您会让所有东西都具有默认构造函数吗?)。 - R. Martinho Fernandes
@R.MartinhoFernandes:谁需要类不变量? - Matthieu M.

5
啊,Xeo还没有露面吗?
好的,我告诉过你一次,现在再说一遍:这两个对象完全不同,用途也不同。
- “unique_ptr”表示“我拥有一个对象”。这只是另一种说法,“我是一个对象”。因此,null unique_ptr是一种可以引起注意的东西。我期望得到一个对象,但我什么都没得到;代码一定有问题! - “optional”表示“我有时可能未被初始化,但没关系”。在这种情况下,不必担心;如果是“None”,那是已经考虑过的行为。
两者都可以隐式转换为bool,并不意味着它们可以互换使用。“optional”适用于可能不会产生任何输出的代码(例如读取流)。在工厂对象中使用“unique_ptr”;它们很可能为您创建一个对象,如果不成功则抛出异常。
关于您的示例的结论:find应返回optional。

2
我不理解为什么会有人害怕空的 unique_ptr。C++ 标准中并没有规定 unique_ptr 不能是空的。而且,对我来说,一个空的 unique_ptr 完美地表达了“未找到”的含义,就像 optional 一样。在我看来,optional 的真正优势在于,你不必像 Matthieu 所说的那样支付动态内存分配的成本。 - Chris Drew

3
概念上来说,考虑到可为空的要求:
- `std::optional` 具有值语义,存储于栈中。 - `std::unique_ptr` 具有移动语义,存储于堆中。
如果您需要值语义和堆存储,请使用 `std::indirect`。详情请查看http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0201r1.pdf
(如果您需要移动语义和栈存储...我不知道。也许是使用带有栈分配器的 `unique_ptr`?)

1
那么有没有理由更喜欢其中一个?
它们表达的意图非常不同:optional表示函数可能不会给你返回结果(这不是错误情况)。unique_ptr告诉你一些关于所有权语义的信息(并且更适合与null一起使用,以表示错误)。
通常我会使用最能表达接口背后意图的那个。
例如,考虑你正在编写一个HTTP服务器,试图将接收到的缓冲区解析为HTTP请求对象。当你尝试解析不完整的缓冲区时,没有错误情况,你只需等待并缓冲更多数据,然后再次尝试。
我会使用optional来表达这一点,以明确函数可能不返回任何内容(而不返回任何内容不是错误情况)。
如果我的解析需要验证某些东西(例如,正则表达式解析器应在解析的表达式无效时产生错误),我会返回一个空的unique_ptr,或者更好的方法是抛出异常。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接