`std :: optional`优于`std :: shared_ptr`和`std :: unique_ptr`的优势是什么?

The*_*ist 6 c++ optional c++17 stdoptional

通过说它可能包含或不包含值std::optional进行推理.因此,如果我们不需要它,它可以节省构建一个可能是大对象的工作量.

例如,如果不满足某些条件,此处的工厂将不会构造对象:

#include <string>
#include <iostream>
#include <optional>

std::optional<std::string> create(bool b) 
{
    if(b)
        return "Godzilla"; //string is constructed
    else
        return {}; //no construction of the string required
}
Run Code Online (Sandbox Code Playgroud)

但那么这与此有何不同:

std::shared_ptr<std::string> create(bool b) 
{
    if(b)
        return std::make_shared<std::string>("Godzilla"); //string is constructed
    else
        return nullptr; //no construction of the string required
}
Run Code Online (Sandbox Code Playgroud)

通过添加std::optional仅仅使用std::shared_ptr一般来说我们获胜的是什么?

Sla*_*ica 10

通过在一般情况下仅使用std :: shared_ptr添加std :: optional,我们获胜的是什么?

假设你需要从一个带有标志"not a value"的函数返回一个符号.如果您将使用std::shared_ptr它,您将有巨大的开销 - char将分配在动态内存中,加上std::shared_ptr将保持控制块.而另一方面std :: optional:

如果可选项包含值,则保证将该值作为可选对象占用空间的一部分进行分配,即不会发生任何动态内存分配.因此,即使定义了运算符*()和运算符 - >(),可选对象也会对对象进行建模,而不是指针.

因此,不涉及动态内存分配,即使与原始指针进行比较也可能很重要.

  • `std :: optional :: value_or`也可以说是值得的 (6认同)

Yak*_*ont 6

可选是可空值类型。

Ashared_ptr是可空的引用计数引用类型。

Aunique_ptr是可以为空的仅移动引用类型。

它们的共同点是它们可以为空——它们可以“不存在”。

它们的不同之处在于,两个是引用类型,另一个是值类型。

值类型有几个优点。首先,它不需要在堆上分配——它可以与其他数据一起存储。这消除了可能的异常来源(内存分配失败),可以更快(堆比堆栈慢),并且对缓存更友好(因为堆往往是相对随机排列的)。

引用类型还有其他优点。移动引用类型不需要移动源数据。

对于仅限非移动的引用类型,您可以通过不同的名称对同一数据进行多个引用。具有不同名称的两种不同的值类型总是引用不同的数据。无论哪种方式,这都可能是优势或劣势;但它确实使有关值类型的推理变得更加容易。

推理shared_ptr是非常困难的。除非对如何使用数据进行非常严格的控制,否则几乎不可能知道数据的生命周期是多少。推理unique_ptr要容易得多,因为您只需要跟踪它移动的位置。推理optional的生命周期是微不足道的(好吧,就像你嵌入它的内容一样微不足道)。

可选接口增加了一些类似 monadic 的方法(如.value_or),但这些方法通常可以很容易地添加到任何可空类型中。尽管如此,目前,它们在那里是为了optional而不是为了shared_ptrunique_ptr

optional 的另一个巨大好处是,您非常清楚有时希望它可以为空。有C ++中的坏习惯推测指针和智能指针不为空,因为它们使用的原因比做空。

因此代码假定某些共享或唯一的 ptr 永远不会为空。它通常有效。

相比之下,如果你有一个可选项,你拥有它的唯一原因是因为它实际上有可能为空。

在实践中,我对将 aunique_ptr<enum_flags> = nullptr作为参数持怀疑态度,我想说“这些标志是可选的”,因为强制调用者分配堆似乎很粗鲁。但是 anoptional<enum_flags>不会强制调用者执行此操作。的非常便宜optional使我愿意在许多情况下使用它,如果我拥有的唯一可空类型是智能指针,我会找到其他一些解决方法。

这消除了对“标志值”的大部分诱惑,例如int rows=-1;. optional<int> rows;具有更清晰的含义,并且在调试中会告诉我何时使用行而不检查“空”状态。

可以合理地失败或不返回任何感兴趣的东西的函数可以避免标志值或堆分配,并返回optional<R>. 例如,假设我有一个可放弃的线程池(例如,一个在用户关闭应用程序时停止处理的线程池)。

我可以std::future<R>从“队列任务”函数返回并使用异常来指示线程池已被放弃。但这意味着线程池的所有使用都必须针对“来自”异常代码流进行审计。

相反,我可以返回std::future<optional<R>>,并提示用户他们必须在他们的逻辑中处理“如果过程从未发生会发生什么”。

“来自”异常仍然可能发生,但它们现在是异常的,不是标准关闭程序的一部分。

在其中一些情况下,expected<T,E>一旦符合标准将是更好的解决方案。