应该如何使用std :: optional?


133

我正在阅读有关的文档 std::experimental::optional并且对它的功能有很好的了解,但是我不知道何时应该使用它或应该如何使用它。该站点目前尚未包含任何示例,这使我更难理解该对象的真实概念。何时使用是std::optional一个不错的选择,它如何弥补以前的标准(C ++ 11)中没有的内容。


19
boost.optional文档可能会提供一些线索。
juanchopanza

似乎std :: unique_ptr通常可以服务相同的用例。我想,如果您对新事物持反对态度,那么可选的也许会更可取,但在我看来,对新事物持这种观点的(开发人员|应用)仅占很小的一部分...事实如此,可选的并不是一个好主意。至少,我们大多数人都可以舒适地没有它。就个人而言,在一个不必在unique_ptr与optional之间进行选择的世界中,我会感到更加自在。叫我疯了,但是Python的Zen是正确的:让一种正确的方法来做某事!
allyourcode

19
我们并不总是希望在堆上分配一些必须删除的东西,因此没有unique_ptr不能替代可选的东西。
克鲁姆

5
@allyourcode没有指针可以代替optional。想象你想要一个optional<int>甚至<char>。您是否真的认为需要动态分配,取消引用然后删除它是“ Zen”-否则就不需要分配并且可以紧凑地放在堆栈中,甚至可以放在寄存器中吗?
underscore_d

1
毫无疑问,所包含值的堆栈与堆分配之间的另一个差异是,在std::optional(虽然可以是std::unique_ptr)的情况下,模板参数不能是不完整的类型。更准确地说,该标准要求T [...]必须满足Destructible的要求
dan_din_pantelimon

Answers:


172

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

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

相同的事情可以通过引用参数来代替(如下面的签名所示),但是使用std::optional可以使签名和用法更好。

bool try_parse_int(std::string s, int& i);

可以这样做的另一种方法特别糟糕

int* try_parse_int(std::string s); //return nullptr if fail

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


另一个例子:

class Contact
{
    std::optional<std::string> home_phone;
    std::optional<std::string> work_phone;
    std::optional<std::string> mobile_phone;
};

std::unique_ptr<std::string>对于每个电话号码都带有一个类似的字母,这是非常可取的!std::optional为您提供数据局部性,这对于提高性能非常有用。


另一个例子:

template<typename Key, typename Value>
class Lookup
{
    std::optional<Value> get(Key key);
};

如果查找中没有特定的键,那么我们可以简单地返回“无值”。

我可以这样使用它:

Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");

另一个例子:

std::vector<std::pair<std::string, double>> search(
    std::string query,
    std::optional<int> max_count,
    std::optional<double> min_match_score);

这比说有四个函数重载要花费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);

如果查询字符串不在中s,则我希望“否int”- 而不是有人为此目的使用的任何特殊值(-1?)。


有关其他示例,请参阅boost::optional 文档boost::optional并且std::optional在行为和用法方面基本相同。


13
@gnzlbg std::optional<T>只是a T和a bool。成员函数的实现非常简单。使用性能时,性能并不是真正要考虑的问题-有时有些东西是可选的,在这种情况下,这通常是完成工作的正确工具。
蒂莫西·希尔兹

8
@TimothyShields std::optional<T>比这复杂得多。它使用放置new以及更多其他具有适当对齐方式和大小的东西,以使其成为文字类型(例如,与一起使用constexpr)。天真的Tbool方法很快就会失败。
拉普兹2014年

15
@Rapptz行256:union storage_t { unsigned char dummy_; T value_; ... }行289:struct optional_base { bool init_; storage_t<T> storage_; ... }不是 “一个T和一个bool”吗?我完全同意该实现非常棘手且不平凡,但在概念上和具体而言,类型为a T和a bool。“幼稚Tbool方法很快就会失败。” 在查看代码时如何做出此声明?
蒂莫西·希尔兹

12
@Rapptz它仍在存储布尔值和int空间。联合仅是使可选参数在不需要时不构造T。仍然struct{bool,maybe_t<T>}是工会不该做的struct{bool,T},这将在所有情况下都构造一个T。
PeterT 2014年

12
@allyourcode很好的问题。双方std::unique_ptr<T>std::optional<T>在一定意义上做到符合角色“可选T”。我将它们之间的区别描述为“实现细节”:额外的分配,内存管理,数据局部性,移动成本等std::unique_ptr<int> try_parse_int(std::string s);。例如,我永远都不会,因为即使没有理由这样做也会导致每个调用分配一个。我永远不会有一个类std::unique_ptr<double> limit;-为什么要分配并丢失数据局部性?
蒂莫西·希尔兹

35

引用的论文中引用了一个示例: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
         }
     }
}

13
因为您可以传递有关是否具有int向上或向下调用层次结构的信息,而不是传递一些“假定”值具有“错误”含义的“幻像”值,而不是传递这些信息。
路易斯·马丘卡

1
@Wiz这实际上是一个很好的例子。它(A)随心所欲地str2int()实现转换,(B)不管获取的方法如何string s,并且(C)通过optional<int>而不是基于一些愚蠢的幻数,bool/引用或动态分配的方式传达完整的含义。正在做。
underscore_d

10

但我不知道何时应该使用它或应该如何使用它。

考虑在编写API时要表达“没有回报”的值不是错误。例如,您需要从套接字读取数据,并在数据块完成后解析并返回它:

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

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket);

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

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 ]
    }
}

编辑:关于其余的问题:

when是std :: optional一个很好的选择

  • 当您计算一个值并需要将其返回时,与引用输出值(可能不会生成)相比,按值返回更好的语义。

  • 当您要确保客户端代码必须检查输出值时(无论谁编写客户端代码,都可能无法检查错误-如果您尝试使用未初始化的指针,则会得到核心转储;如果您尝试使用未初始化的指针,初始化的std :: optional,您将获得一个可捕获的异常)。

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

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

两者都使客户端实现者付出了额外的精力和精力来使它正确无误,并且两者都造成了混乱(第一个促使客户端实现者将操作视为一种分配,并且要求客户端代码实现指针处理逻辑,第二个则允许客户端代码以摆脱使用无效/未初始化的值)。

std::optional 很好地解决了先前解决方案所产生的问题。


我知道它们基本上是相同的,但是为什么使用boost::而不是std::
0x499602D2

4
谢谢-我更正了(我曾经 boost::optional它是因为使用了大约两年后,它被硬编码到我的前额叶皮层中)。
utnapistim

1
这使我感到不满意,因为如果完成了多个块,则只能返回一个,其余的将被丢弃。该函数应该返回可能为空的块集合。
Ben Voigt

你是对的; 这是一个糟糕的例子;我已经修改了“解析第一个块(如果可用)”的示例。
utnapistim

4

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

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

举一个实际的例子,我从XML文档中提取了一个几何图形,该几何图形的值称为渲染标志,其中,该几何可以覆盖渲染标志(设置),禁用渲染标志(设置为0),或者根本不影响渲染标志(未设置),可选的方法是一种清晰的表示方式。

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

我相信这与代码的清晰度有关。清晰度降低了代码维护和开发的成本。清楚地了解代码意图非常有价值。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.