我正在阅读std::experimental::optional
的文档,我对它的作用有一个很好的了解,但我不明白何时我应该使用它或我应该如何使用它。该网站不包含任何例子,这使我更难理解这个对象的真实概念。什么时候std::optional
是一个很好的选择,它如何补偿前一个标准(C ++ 11)中没有的内容。
答案 0 :(得分:161)
我能想到的最简单的例子:
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
(或不是)的每种可能组合的四个函数重载更有意义!
消除 被诅咒“-1
max_count
如果你不想要限制”或“通过{{1}如果你不想要最低分数,那么std::numeric_limits<double>::min()
就可以了!“
另一个例子:
min_match_score
如果查询字符串不在std::optional<int> find_in_string(std::string s, std::string query);
中,我想要“不s
” - 不某人为此目的决定使用的任何特殊值(-1) ?)。
有关其他示例,您可以查看int
documentation。 <{1}}和boost::optional
在行为和使用方面基本相同。
答案 1 :(得分:31)
示例引自New adopted 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
}
}
}
答案 2 :(得分:9)
但我不明白何时应该使用它或我应该如何使用它。
考虑何时编写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,或接受输出参数并返回错误/结果代码“不可用”。
两者都要求客户端实现者付出额外的努力和注意力以使其正确并且两者都是混淆的源头(第一个推动客户端实现者将操作视为分配并要求客户端代码实现指针处理逻辑和第二个允许客户端代码使用无效/未初始化的值来逃避。
std::optional
很好地处理以前的解决方案所带来的问题。
答案 3 :(得分:4)
我经常使用选项来表示从配置文件中提取的可选数据,也就是说,可选地提供了数据(例如XML文档中的预期但非必要的元素),以便我可以明确地和清楚地显示数据是否实际存在于XML文档中。特别是当数据可以具有&#34;未设置&#34;国家,而不是&#34;空&#34;和&#34;设置&#34;状态(模糊逻辑)。如果有一个可选的,设置和未设置是清除的,那么空值也会清零,值为0或null。
这可以显示&#34;未设置&#34;的值。不等于&#34;空&#34;。在概念上,指向int(int * p)的指针可以显示这一点,其中未设置null(p == 0),设置值0(* p == 0)并且为空,以及任何其他值(* p&lt;&gt; 0)被设置为值。
对于一个实际的例子,我从XML文档中提取了一个几何图形,该文档具有一个名为render flags的值,其中几何图形可以覆盖渲染标记(set),禁用渲染标记(设置为0),或者根本不影响渲染标志(未设置),可选的是一种表示这种情况的明确方法。
显然,在这个例子中,一个指向int的指针可以实现目标,或者更好,一个共享指针,因为它可以提供更清晰的实现,但是,在这种情况下,我认为它是关于代码清晰度的。是null总是&#34;没有设置&#34;?使用指针,它不清楚,因为null字面意思是没有分配或创建,虽然它可以,但可能不一定意味着&#34;没有设置&#34; 。值得指出的是,必须释放指针,并且在良好实践中设置为0,但是,与共享指针一样,可选的不需要显式清理,因此不必担心混淆没有设置可选项的清理。
我相信代码清晰度。 Clarity降低了代码维护和开发的成本。清楚地理解代码意图是非常有价值的。
使用指针来表示这将需要重载指针的概念。表示&#34; null&#34;因为&#34;未设置&#34;,通常您可能会通过代码看到一个或多个注释来解释此意图。这不是一个糟糕的解决方案,而不是一个可选的,但是,我总是选择隐式实现而不是显式注释,因为注释不可执行(例如通过编译)。这些用于开发的隐式项目的示例(那些纯粹为了强制执行而提供的开发中的文章)包括各种C ++样式转换,&#34; const&#34; (特别是关于成员函数),&#34; bool&#34;类型,仅举几例。可以说,只要每个人都服从意图或评论,你就不需要这些代码功能。