如何使用std::optional ?

How should one use std::optional?

本文关键字:optional std 何使用      更新时间:2023-10-16

我正在阅读std::experimental::optional的文档,我对它的作用有一个很好的想法,但我不明白何时我应该使用它或如何使用它。这个网站还没有包含任何例子,这让我很难掌握这个对象的真正概念。什么时候使用std::optional是一个好的选择,它如何弥补以前的标准(c++ 11)中没有找到的东西。

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

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);
};

如果查找中没有某个键,则可以简单地返回"no value"。

我可以这样使用:

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中,我想要"no int "——not任何人决定为此目的使用的任何特殊值(-1?)


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

引用New adopt paper: 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
         }
     }
}

但我不明白什么时候该用,怎么用

考虑当你在编写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 ]
    }
}

编辑:关于你剩下的问题:

当std::optional是使用

的好选择时
  • 当您计算一个值并需要返回它时,按值返回比引用输出值(可能不会生成)具有更好的语义。

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

[…]以及它如何补偿以前的标准(c++ 11)中没有找到的内容。

在c++ 11之前,你必须为"可能不返回值的函数"使用不同的接口——要么通过指针返回并检查是否为NULL,要么接受输出参数并返回错误/结果代码"not available"。

两者都需要客户端实现者付出额外的努力和注意力才能正确完成,两者都是混乱的根源(第一个要求客户端实现者将操作视为分配,并要求客户端代码实现指针处理逻辑,第二个允许客户端代码使用无效/未初始化的值)。

std::optional很好地处理了先前解决方案中出现的问题。

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

这可以显示"not set"的值如何不等同于"empty"。从概念上讲,指向int (int *p)的指针可以显示这一点,其中未设置null (p == 0),设置0值(*p == 0)并为空,并将任何其他值(*p <> 0)设置为值。

对于一个实际的例子,我有一个从XML文档中提取的几何体,它的值称为呈现标志,其中几何体可以覆盖呈现标志(设置),禁用呈现标志(设置为0),或者简单地不影响呈现标志(未设置),可选将是一个清晰的方式来表示这一点。

很明显,在这个例子中,指向int的指针可以达到这个目标,或者更好的是,共享指针可以提供更清晰的实现,但是,我认为在这个例子中,它是关于代码清晰度的。null总是"未设置"吗?对于指针,这是不清楚的,因为null字面上表示未分配或未创建,尽管它可以,但不一定表示"未设置"。值得指出的是,指针必须被释放,并且在良好的实践中必须设置为0,然而,与共享指针一样,可选指针不需要显式清除,因此不需要担心将清除与未设置的可选指针混淆。

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

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