std::optional 相对于 nullptr 的优点/缺点是啥?
Posted
技术标签:
【中文标题】std::optional 相对于 nullptr 的优点/缺点是啥?【英文标题】:What are the advantages/disadvantages of std::optional over nullptr?std::optional 相对于 nullptr 的优点/缺点是什么? 【发布时间】:2021-12-18 15:26:21 【问题描述】:我在互联网上看了几部在线 std::optional 纪录片。但是,我无法找到以下两种情况之间的任何直接比较:
案例 1:
SomePointer* foo::get_some_pointer(cont int value)
auto result = myMap.find(value);
if (result != myMap.end())
return const_cast<SomePointer*>(&result->second);
return nullptr;
案例2
std::optional<SomePointer*> foo::get_some_pointer (cont int value)
auto result = myMap.find(value);
if (result != myMap.end())
return std::optional<SomePointer*>&result->second;
return std::nullopt;
案例 1 相对于案例 2 的优点/缺点是什么(nullopt 优于 nullptr)?
【问题讨论】:
所以,为了返回一个 ponter,实际上不需要将它包装到 std::optional 因为你有nullptr
。但是,如果您返回一些没有“空”状态的对象或值,std::optional
会非常有用。
案例2有更多的空值,它同时支持nullptr
和std::nullopt
,更多的选择:)。更常见的是使用std::optional<SomePointer>
,而不使用*
。
顺便说一句,const_cast
非常粗略。
带有std::optional<SomePointer*>
,它有三种状态:有一个有效的指针,有一个nullptr
,有一个std::nullopt
。您的代码小心地返回有效指针或std::nullopt
...但调用者或此后的代码仍应警惕“永远不会发生”nullptr
的可能性。 (嗯,可能有一个悬空指针,或者一个野指针,或者未初始化的指针......我忽略了这些场景。)
为了您的考虑,返回std::optional<std::reference_wrapper<SomePointer>>
,它完全避免了nullptr
状态,并且仍然具有打包行为,并且是自记录代码。 (SomePointer 的名字可能很糟糕,因为我认为它不是指针。)
【参考方案1】:
std::optional
的唯一工作是通过额外的“null”值扩展类型域。每个指针类型 T*
已经有一个被视为“null”的值 - nulltpr
。
因此,直接比较这两者并不是一个好主意,因为它们回答的问题不同。有时区分“无结果”和“空结果”1(这是一种可能的解释)很重要,有时则不然。您应该使用适合您需要的任何一种。
现在如果代码返回指针的唯一原因是利用隐式指针可空性,那么正确的解决方案是将其更改为返回std::optional<SomePointer>
(或者可能是std::optional<std::reference_wrapper<SomePointer>>
) 代替,但这不是你问的。
1 当然在这一点上考虑类似的东西也是值得的。 struct NoResult ; using Result = std::variant<NoResult, SomePointer*>;
使其更加明确。
【讨论】:
@MericOzcan 在当前代码中,您在第二个示例中调用at
,这是在进行不必要的查找。除此之外,optional<T*>
在T*
之上的开销应该可以忽略不计(如果有的话)。在这两种情况下都没有指向的值的副本。
@MericOzcan 首先,您需要考虑(强制)返回值优化,这意味着返回值直接在应分配的位置创建。这消除了按价值返回的复制成本。除了包含值的std::optional
和原始指针(或者可能是std::unique_ptr
!)之间,应用与非可选对象之间相同的参数,按值或按指针...
@MericOzcan 这完全取决于您的具体要求!如果someObject
是一个局部变量,return &someObject;
是非法的(你以这种方式返回一个 dangling 指针!)。按值返回允许在堆栈上分配对象(好吧,假设std::optional
是使用placement-new 和显式析构函数调用实现的,不确定是否由标准强制),出于性能原因,有时可能更可取。返回原始指针可能意味着必须显式返回 delete
对象(在这种情况下,std::unique_ptr
通常是首选)。
如果您需要返回已经存在的对象,这些对象的生命周期由另一个组件控制,原始指针是不错的选择,可以避免任何不必要甚至错误的副本。
@MericOzcan 好吧,那么考虑以下区别:如果您不希望能够返回空/无结果值——您何时按值返回,何时按引用返回?通过值意味着您获得对象的副本,通过引用您仍然引用原始对象。同样适用于std::optional
与原始指针,只是现在您根本无法返回任何对象...以上是关于std::optional 相对于 nullptr 的优点/缺点是啥?的主要内容,如果未能解决你的问题,请参考以下文章
将一个 std::optional 转换为另一个 std::optional