我正在阅读 的文档,std::experimental::optional
并且对它的作用有一个很好的了解,但我不明白什么时候应该使用它或应该如何使用它。该站点尚未包含任何示例,这使我更难掌握该对象的真实概念。何时使用是std::optional
一个不错的选择,以及它如何弥补以前标准 (C++11) 中未找到的内容。
4 回答
我能想到的最简单的例子:
std::optional<int> try_parse_int(std::string s)
{
//try to parse an int from the given string,
//and return "nothing" if you fail
}
可以使用引用参数来完成相同的事情(如以下签名中所示),但 usingstd::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
如果你不想要限制就通过”或“std::numeric_limits<double>::min()
如果min_match_score
你不想要最低分数就通过”!
另一个例子:
std::optional<int> find_in_string(std::string s, std::string query);
如果查询字符串不在 中s
,我想要“no int
”——而不是某人决定为此目的使用的任何特殊值(-1?)。
有关其他示例,您可以查看boost::optional
文档。boost::optional
并且std::optional
在行为和使用方面基本相同。
An example is quoted from 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
}
}
}
但我不明白什么时候应该使用它或应该如何使用它。
考虑一下当您编写 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
很好地解决了以前的解决方案出现的问题。
我经常使用 optionals 来表示从配置文件中提取的可选数据,也就是说,数据(例如 XML 文档中的预期但不是必需的元素)是可选地提供的,这样我就可以明确而清晰地显示是否数据实际上存在于 XML 文档中。特别是当数据可以具有“未设置”状态,而不是“空”和“设置”状态(模糊逻辑)时。有一个可选的,设置和未设置是明确的,空的也将是明确的,值为 0 或 null。
这可以显示“未设置”的值如何不等于“空”。从概念上讲,指向 int (int * p) 的指针可以显示这一点,其中未设置 null (p == 0),设置值为 0 (*p == 0) 并为空,以及任何其他值(*p <> 0) 设置为一个值。
对于一个实际示例,我从 XML 文档中提取了一个几何图形,该几何图形具有一个称为渲染标志的值,其中几何图形可以覆盖渲染标志(设置),禁用渲染标志(设置为 0),或者根本不影响渲染标志(未设置),可选的将是表示这一点的清晰方法。
显然,在本例中,指向 int 的指针可以实现目标,或者更好的是共享指针,因为它可以提供更清晰的实现,但是,我认为在这种情况下它是关于代码清晰度的。空值总是“未设置”吗?对于指针,尚不清楚,因为 null 字面意思是未分配或未创建,尽管它可以,但不一定表示“未设置”。值得指出的是,必须释放指针,并且在良好实践中设置为 0,但是,与共享指针一样,可选项不需要显式清理,因此不必担心将清理与选项尚未设置。
我相信这是关于代码的清晰度。Clarity 降低了代码维护和开发的成本。清楚地理解代码意图是非常有价值的。
使用指针来表示这一点需要重载指针的概念。要将“null”表示为“未设置”,通常您可能会通过代码看到一个或多个注释来解释此意图。这不是一个糟糕的解决方案而不是一个可选的解决方案,但是,我总是选择隐式实现而不是显式注释,因为注释是不可强制执行的(例如通过编译)。这些用于开发的隐式项的示例(那些纯粹为强制执行意图而提供的开发中的文章)包括各种 C++ 样式转换、“const”(尤其是成员函数)和“bool”类型,仅举几例。可以说,您并不真正需要这些代码功能,只要每个人都遵守意图或注释即可。