原文地址
Intro
float divide(float a, float b)
{if (b == 0){return ?;}return a / b;
}
这里以一个除法函数为例,当 b 为 0 的时候,明显是除法的异常,但是怎样把这个状态返回给调用方呢?
常见的方法有如下几种
- 抛异常,当除0时抛出相关的异常,调用方捕获异常
- 返回一个特殊的值 (-1, infinity, nullptr) 这种需要和调用方约定好,语义不是很清晰。
- Optional types. 这是一种类型安全并且表达力更强。
std::optional
在 c++17中被加入,以前的c++标准可以通过 boost::optional
来获取。在c++17中可以使用#include <optional>
来使用这个类型。
经过 std::optional
封装的仍然是一个值类型,可以拷贝。 std::optional
不需要在堆上申请内存。
When to use
通常,可能在这些场景使用 std::optional
封装
- 需要较好的表示空类型 (nullable type)
- 替代使用特殊值 (-1, nullptr, NO_VALUE 或者其他)
- 比如某些值可能存在可能不存在,但是我们又需要知道是否存在, 比如,在统计年龄的时候,可能有的数据里面没有年龄,这个时候我们可以使用
std::optional<int>
表示。
- 返回一个执行失败的结果
- 比如除0 错误,不能得到正确的值的时候。
- 懒加载资源的时候
- 比如,一个 resource 类型没有默认的构造函数,但是构造的工作 比较庞大,可以使用
std::optional<Resource>
来定义,然后在需要的时候来加载。 - 在传参的时候
- 比如,一个 resource 类型没有默认的构造函数,但是构造的工作 比较庞大,可以使用
Basic Example
std::optional<std::string> UI::FindUserNick()
{if (nick_available)return { mStrNickName };return std::nullopt; // same as return { };
}// use:
std::optional<std::string> UserNick = UI->FindUserNick();
if (UserNick)Show(*UserNick);
Creation
// empty:
std::optional<int> oEmpty;
std::optional<float> oFloat = std::nullopt;// direct:
std::optional<int> oInt(10);
std::optional oIntDeduced(10); // deduction guides// make_optional
auto oDouble = std::make_optional(3.0);
auto oComplex = make_optional<std::complex<double>>(3.0, 4.0);// in_place
std::optional<std::complex<double>> o7{std::in_place, 3.0, 4.0};// will call vector with direct init of {1, 2, 3}
std::optional<std::vector<int>> oVec(std::in_place, {1, 2, 3});// copy/assign:
auto oIntCopy = oInt;
获取值
// by operator*
std::optional<int> oint = 10;
std::cout<< "oint " << *opt1 << '\n';// by value()
std::optional<std::string> ostr("hello");
try
{std::cout << "ostr " << ostr.value() << '\n';
}
catch (const std::bad_optional_access& e)
{std::cout << e.what() << "\n";
}// by value_or()
std::optional<double> odouble; // empty
std::cout<< "odouble " << odouble.value_or(10.0) << '\n';
一种稳妥的做法时在访问的时候判断是否为空
// compute string function:std::optional<std::string> maybe_create_hello(); // ... if (auto ostr = maybe_create_hello(); ostr)std::cout << "ostr " << *ostr << '\n'; else std::cout << "ostr is null\n";
修改值
#include <optional>
#include <iostream>
#include <string>class UserName
{
public:explicit UserName(const std::string& str) : mName(str){ std::cout << "UserName::UserName(\'";std::cout << mName << "\')\n"; }~UserName() {std::cout << "UserName::~UserName(\'";std::cout << mName << "\')\n"; }private:std::string mName;
};int main()
{std::optional<UserName> oEmpty;// emplace:oEmpty.emplace("Steve");// calls ~Steve and creates new Mark:oEmpty.emplace("Mark");// reset so it's empty againoEmpty.reset(); // calls ~Mark// same as://oEmpty = std::nullopt;// assign a new value:oEmpty.emplace("Fred");oEmpty = UserName("Joe");
}