应该如何使用std :: optional?

0x4*_*2D2 120 c++ boost-optional c++-tr2

我正在阅读文档,std::experimental::optional我对它的作用有很好的了解,但我不明白何时应该使用它或我应该如何使用它.该网站不包含任何例子,这使我更难理解这个对象的真实概念.何时是std::optional一个很好的选择,它如何补偿以前的标准(C++ 11)中没有找到的东西.

Tim*_*lds 167

我能想到的最简单的例子:

std::optional<int> try_parse_int(std::string s)
{
    //try to parse an int from the given string,
    //and return "nothing" if you fail
}
Run Code Online (Sandbox Code Playgroud)

使用引用参数可以完成相同的操作(如下面的签名),但使用std::optional使签名和使用更好.

bool try_parse_int(std::string s, int& i);
Run Code Online (Sandbox Code Playgroud)

另一种可以做到这一点的方法尤其糟糕:

int* try_parse_int(std::string s); //return nullptr if fail
Run Code Online (Sandbox Code Playgroud)

这需要动态内存分配,担心所有权等等 - 总是更喜欢上面其他两个签名中的一个.


另一个例子:

class Contact
{
    std::optional<std::string> home_phone;
    std::optional<std::string> work_phone;
    std::optional<std::string> mobile_phone;
};
Run Code Online (Sandbox Code Playgroud)

std::unique_ptr<std::string>对于每个电话号码而言,这是非常可取的!std::optional为您提供数据位置,这对性能非常有用.


另一个例子:

template<typename Key, typename Value>
class Lookup
{
    std::optional<Value> get(Key key);
};
Run Code Online (Sandbox Code Playgroud)

如果查找中没有某个键,那么我们可以简单地返回"无值".

我可以像这样使用它:

Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");
Run Code Online (Sandbox Code Playgroud)

另一个例子:

std::vector<std::pair<std::string, double>> search(
    std::string query,
    std::optional<int> max_count,
    std::optional<double> min_match_score);
Run Code Online (Sandbox Code Playgroud)

这比使用四个函数重载更有意义,它们采用max_count(或不)和min_match_score(或不)的每种可能组合!

它也消除诅咒 "通过-1max_count,如果你不想限制"或"合格std::numeric_limits<double>::min()min_match_score,如果你不想要一个最低分"!


另一个例子:

std::optional<int> find_in_string(std::string s, std::string query);
Run Code Online (Sandbox Code Playgroud)

如果查询字符串不在s,我想要"不int" - 不是某人为此目的决定使用的特殊值(-1?).


有关其他示例,您可以查看boost::optional 文档.boost::optional并且std::optional在行为和使用方面基本相同.

  • @Rapptz第256行:`union storage_t {unsigned char dummy_; T值_; ...}`第289行:`struct optional_base {bool init_; storage_t <T> storage_; ......}`那个*不是*"一个`T`和一个`bool`"?我完全同意实现是非常棘手和不平凡的,但从概念上和具体上来说,类型是`T`和`bool`."天真的`T`和`bool`方法会很快失败." 在查看代码时如何做出这样的陈述? (15认同)
  • @gnzlbg`std :: optional <T>`只是一个`T`和一个`bool`.成员函数实现非常简单.在使用它时,性能不应该成为一个问题 - 有时候某些东西是可选的,在这种情况下,这通常是工作的正确工具. (13认同)
  • @Rapptz它仍然存储bool和int的空间.联盟只是在那里使可选的不构造T,如果它实际上不需要.它仍然是`struct {bool,maybe_t <T>}`联合就是没有`struct {bool,T}`,它会在所有情况下构造一个T. (12认同)
  • @allyourcode非常好的问题.`std :: unique_ptr <T>`和`std :: optional <T>`在某种意义上都可以实现"可选的`T`"的作用.我会将它们之间的区别描述为"实现细节":额外的分配,内存管理,数据局部性,移动成本等.我永远不会有`std :: unique_ptr <int> try_parse_int(std :: string s);`例如,因为它会导致每个呼叫的分配,即使没有理由.我永远不会有一个带有`std :: unique_ptr <double>限制的类;` - 为什么要进行分配并丢失数据局部性? (12认同)
  • @TimothyShields`std :: optional <T>`复杂得多.它使用放置`new`和更多其他具有正确对齐和大小的东西,以使其成为文字类型(即使用`constexpr`)等.天真的`T`和`bool`方法会很快失败. (8认同)
  • @Rapptz我并不反对你,但除非你发布一个链接到`std :: optional <T>`的实现或解释更多,我觉得你没有贡献任何东西.我的"a`T`和`bool`"陈述的重点只是试图揭开这种类型的神秘面纱.我不能立即访问C++ 11标准库实现,以实际知道它们是仅使用`T`和`bool`还是使用一些必要的其他技巧来获得`constexpr`功能. (6认同)
  • 对于"消除被诅咒"的部分+1,更多的是强调. (4认同)
  • @TimothyShields因为在*templates*,`std :: optional <int>`的上下文中,`T`有`int`.这不是存储`int`和`bool`,而是存储类型的union(它应该是`std :: aligned_storage <int>`IMO).它根本不同.因此,为什么天真的"T"方法会失败(默认的可构造性要求等). (3认同)
  • @allyourcode争论指针_ever_"作为C++中的默认选择"完全违背了现代风格,正确地强调价值语义,而不是担心分配和生命周期.当存在保留值语义和更好的语法的更好选项时,通过动态分配某些东西的所有麻烦和性能限制是没有意义的. (3认同)
  • 请注意,您可以通过“return std::nullopt;”返回“nothing”作为可选值 (2认同)

tao*_*ocp 32

一个例子引自新收养的论文:N3672,std :: optional:

 optional<int> str2int(string);    // converts int to string if possible

int get_int_from_user()
{
     string s;

     for (;;) {
         cin >> s;
         optional<int> o = str2int(s); // 'o' may or may not contain an int
         if (o) {                      // does optional contain a value?
            return *o;                  // use the value
         }
     }
}
Run Code Online (Sandbox Code Playgroud)

  • 因为你可以_pass_关于你是否有一个`int`或者没有向上或向下调用层次结构的信息,而不是传递一些"假定"值"假定"具有"错误"含义. (12认同)

utn*_*tim 9

但我不明白何时应该使用它或我应该如何使用它.

考虑何时编写API并且您想表达"没有返回"值不是错误.例如,您需要从套接字读取数据,并在数据块完成时解析它并返回它:

class YourBlock { /* block header, format, whatever else */ };

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket);
Run Code Online (Sandbox Code Playgroud)

如果附加数据完成了可解析块,则可以对其进行处理; 否则,继续阅读和追加数据:

void your_client_code(some_socket_object& socket)
{
    char raw_data[1024]; // max 1024 bytes of raw data (for example)
    while(socket.read(raw_data, 1024))
    {
        if(auto block = cache_and_get_block(raw_data))
        {
            // process *block here
            // then return or break
        }
        // else [ no error; just keep reading and appending ]
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:关于其他问题:

什么时候std :: optional是一个很好的选择

  • 当您计算一个值并需要返回它时,它会使值更好地返回语义,而不是引用输出值(可能不会生成).

  • 当你想确保客户端代码必须检查输出值时(编写客户端代码的人可能不会检查错误 - 如果你试图使用未初始化的指针,你会得到核心转储;如果你试图使用un-初始化std :: optional,你得到一个可以捕获的异常).

[...]以及它如何弥补之前标准(C++ 11)中没有的内容.

在C++ 11之前,您必须为"可能不返回值的函数"使用不同的接口 - 通过指针返回并检查NULL,或接受输出参数并返回"不可用"的错误/结果代码".

两者都给予客户端实现者额外的努力和关注以使其正确并且两者都是混淆的源头(第一个推动客户端实现者将操作视为分配并要求客户端代码实现指针处理逻辑而第二个允许使用无效/未初始化值的客户端代码).

std::optional 很好地处理以前解决方案中出现的问题.

  • 谢谢 - 我纠正了它(我使用`boost :: optional`因为使用它大约两年后,它被硬编码到我的前额皮质中). (4认同)

Kit*_*t10 6

我经常使用可选项来表示从配置文件中提取的可选数据,也就是说,该数据(例如 XML 文档中预期的但不是必需的元素)是可选提供的,以便我可以明确并清楚地显示数据实际上存在于 XML 文档中。特别是当数据可以具有“未设置”状态,而不是“空”和“设置”状态(模糊逻辑)时。对于可选,set 和 not set 是明确的,也可以通过值为 0 或 null 来清除空。

这可以说明“未设置”的值如何不等同于“空”。在概念上,指向 int (int * p) 的指针可以显示这一点,其中未设置 null (p == 0),设置值为 0 (*p == 0) 且为空,以及任何其他值(*p <> 0) 设置为一个值。

举一个实际的例子,我从一个 XML 文档中提取了一个几何图形,它有一个名为渲染标志的值,其中几何图形可以覆盖渲染标志(设置),禁用渲染标志(设置为 0),或者根本不影响渲染标志(未设置),可选将是表示这一点的明确方式。

显然,在本例中,指向 int 的指针可以实现目标,或者更好的是,使用共享指针,因为它可以提供更清晰的实现,但是,在这种情况下,我认为这与代码清晰度有关。空值总是“未设置”吗?对于指针,尚不清楚,因为 null 字面意思是未分配或未创建,尽管它可以,但不一定意味着“未设置”。值得指出的是,必须释放一个指针,并且在良好的实践中设置为 0,但是,与共享指针一样,可选不需要显式清理,因此不必担心将清理与可选的尚未设置。

我相信这是关于代码清晰度的。Clarity 降低了代码维护和开发的成本。对代码意图的清晰理解是非常有价值的。

使用指针来表示这将需要重载指针的概念。要将“null”表示为“not set”,通常您可能会通过代码看到一个或多个注释来解释这一意图。这不是一个糟糕的解决方案,而不是一个可选的解决方案,但是,我总是选择隐式实现而不是显式注释,因为注释是不可强制执行的(例如通过编译)。这些用于开发的隐式项的示例(开发中提供的那些纯粹是为了强制执行意图的文章)包括各种 C++ 样式强制转换、“const”(特别是在成员函数上)和“bool”类型,仅举几例。可以说,您并不真正需要这些代码功能,只要每个人都遵守意图或意见即可。