应该如何使用std :: optional?
我正在阅读std::experimental::optional
的文档,我对它做了什么有一个很好的想法,但我不明白什么时候应该使用它,或者我应该如何使用它。 该网站迄今尚未包含任何示例,这让我很难理解这个对象的真实概念。 什么时候std::optional
是一个很好的select,它是如何弥补以前的标准(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
这需要dynamic的内存分配,担心所有权等 – 总是喜欢上面的其他签名之一。
另一个例子:
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
(或不))的函数重载有更多的意义。
如果你不想要最低分数的话,它也消除了可能的“如果你不想要一个限制, max_count
传递-1
”或者为“ max_count
传递std::numeric_limits<double>::min()
”!
另一个例子:
std::optional<int> find_in_string(std::string s, std::string query);
如果查询string不在s
,我想要“不是int
” – 不是为了这个目的决定使用什么特殊的值(-1?)。
有关其他示例,可以查看boost::optional
文档 。 boost::optional
和std::optional
在行为和用法上基本相同。
一个例子是从新收录的文章中引用的: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,你想expression“没有返回”值不是一个错误。 例如,您需要从套接字中读取数据,并在数据块完成时parsing并返回:
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 ::可选是一个很好的select
-
当你计算一个值并且需要返回它的时候,通过值来返回更好的语义,而不是引用一个输出值(可能不会产生)。
-
当你想确保客户端代码必须检查输出值(谁写的客户端代码可能不检查错误 – 如果你尝试使用未初始化的指针,你会得到一个核心转储;如果你试图使用un-初始化std ::可选,你得到一个可捕获的exception)。
它如何弥补以前的标准(C ++ 11)中没有的东西。
在C ++ 11之前,您必须使用不同的接口来“可能不返回值的函数” – 通过指针返回并检查NULL,或者接受输出参数并返回错误/结果代码“not available ”。
两者都要求客户实现者付出额外的努力和注意力,这两者都是造成混淆的原因(第一个推动客户端实现者将操作视为分配,并要求客户端代码实现指针处理逻辑,第二个允许客户端代码脱离使用无效/未初始化的值)。
std::optional
很好地照顾了以前的解决scheme出现的问题。
我经常使用可选项来表示从configuration文件中提取的可选数据,也就是说可选地提供数据的位置(例如,在XML文档中的预期但不是必需的元素),以便我可以明确地清楚地显示数据实际上存在于XML文档中。 特别是当数据可以具有“未设定”状态时,相对于“空”和“设定”状态(模糊逻辑)。 使用可选项,设置和未设置是清楚的,也将清空值为0或null。
这可以显示“未设置”的值如何不等于“空”。 在概念上,指向int(int * p)的指针可以显示这一点,其中null(p == 0)未被设置,值为0(* p == 0)被设置并且为空,并且任何其他值(* p <> 0)被设置为一个值。
举一个实际的例子,我从一个叫做渲染标志的XML文档中取出了一个几何体,其中几何体可以覆盖渲染标志(set),禁用渲染标志(设置为0),或者根本不影响渲染标志(未设置),一个可选的将是一个明确的方式来表示这一点。
很明显,在这个例子中,指向int的指针可以实现目标,或者更好地实现共享指针,因为它可以提供更清晰的实现,但是,我认为这是关于代码清晰度的。 空始终是“未设置”? 有了一个指针,就不清楚了,因为null的字面意思是没有分配或创build,虽然它可能 ,但可能不一定意味着“未设置”。 值得指出的是,必须释放一个指针,然后在良好的实践中将其设置为0,但是像使用共享指针一样,可选项不需要显式清理,所以不需要将清理与可选没有设置。
我相信这是关于代码清晰度的。 清晰度降低了代码维护和开发的成本。 对代码意图的清楚理解是非常有价值的。
使用指针来表示这将需要重载指针的概念。 为了将“null”表示为“未设置”,通常可以通过代码看到一个或多个注释来解释这个意图。 这不是一个不错的解决scheme,而是一个可选的,但是,我总是select隐式的实现,而不是显式的注释,因为注释是不可强制执行的(如通过编译)。 这些用于开发的隐式项目(纯粹为强制意图而提供的那些项目)的例子包括各种C ++风格的转换,“const”(特别是在成员函数上)和“bool”types等等。 可以说,只要每个人都服从意图或评论,你并不需要这些代码function。