引言
在编写可选择接受或返回对象的函数的时候,通常的做法是选择一个单独的布尔值来确保函数入参或者返回对象的可用性:
代码语言:javascript复制//使用is_valid来指示入参value是否有效
void maybe_take_an_int(int value = -1, bool is_valid = false)
//使用bool代表返回的int是否有效
bool maybe_return_an_int(int &value) //or
std::pair<int, bool> maybe_return_an_int()
上面的例子是可行的,但是本来我们只是为了传递或者得到一个value
,但为了确保value
的可用性需要增加一个布尔作为可用的指示,当调用者忘记bool
简单调用maybe_take_an_int(1)
时,该函数会默认失败,同样的在读取bool maybe_return_an_int(int &value)
中的value
值的时候,若忽略返回bool
的值的时候,可能会导致读取的value
是无效的。虽然在std::pair<int, bool> maybe_return_an_int()
中使用了pair
看似将两者进行了绑定,但是还是不能避免使用者忘记检查bool
,导致使用了不可用的value
。
C 17中提供了std::optional<T>
来解决这类问题,我们可以将optional<T>
看作是T
类型和bool
的一个打包。其与std::pair<T, bool>
相比其显示的表达意图,更加可读,而且可以良好地处理构造开销高昂的对象。使用std::optional<T>
,上面的问题可变为
void maybe_take_an_int(optional<int> potential_value = nullopt);
optional<int> maybe_return_an_int();
optional<T>
直接解决传递或存储当前可能或可能不是对象时出现的问题。optional<T>
提供接口来确定它是否包含 并 T
查询存储的值。我们可以使用实际T
值初始化 ,optional
或者默认初始化它(或初始化为 std::nullopt
)以将其置于“空”状态。
注:
std::nullopt_t
是空类类型,用于指示optional
类型拥有未初始化状态。
std::optional基本用法介绍
std::optional<T>
是一个管理一个可选的容纳值(既可以存在,也可以不存在的值)的类模板。任何一个std::optional<T>
实例在给定的时间点要么含值,要么不含值。其在 <optional>
定义,函数原型如下:
template< class T >
class optional; //C 17 起
- T:要为管理状态的值的类型,该校类型需要满足可析构克的要求。(特别是不允许数组类型)
成员函数
构造函数
代码语言:javascript复制//构造不含 值的对象。
constexpr optional() noexcept; //C 17 起
constexpr optional( std::nullopt_t ) noexcept; //C 17 起
/*复制构造函数:如果 other 包含值,那么初始化所含值,
如同以表达式 *other 直接初始化(但不是直接列表初始化)T 类型对象。
如果 other 不含值,那么构造一个不含 值的对象。*/
constexpr optional( const optional& other ); //C 17 起
/*移动构造函数:如果 other 含值,那么初始化所含值,
如同以表达式 std::move(*other) 直接初始化(但不是直接列表初始化) T 类型对象,
且不 令 other 为空:被移动的 std::optional 仍然包含 值,但该值自身是被移动的。*/
constexpr optional( optional&& other ) noexcept(); //C 17 起
/*转换复制构造函数:如果 other 不含值,那么构造不含值的 optional 对象。
否则,构造含值的 optional 对象,如同以表达式 *other 直接初始化
(但不是直接列表初始化) T 类型对象一般初始化。*/
template < class U >
optional( const optional<U>& other ); //C 17 起, C 20 前 (条件性 explicit)
template < class U >
constexpr optional( const optional<U>& other ); //C 20 起 (条件性 explicit)
/*转换移动构造函数:如果 other 不含值,那么构造不含值的 optional 对象。
否则,构造含值的 optional 对象,如同以表达式 std::move(*other) 直接初始化
(但不是直接列表初始化) T 类型对象一般初始化。*/
template < class U >
optional( optional<U>&& other ); //C 17 起, C 20 前 (条件性 explicit)
template < class U >
constexpr optional( optional<U>&& other ); //C 20 起 (条件性 explicit)
/*构造一个包含的对象,如同从参数 std::forward<Args>(args)...
直接初始化(但不是直接列表初始化) T 类型对象一般初始化。r*/
template< class... Args >
constexpr explicit optional( std::in_place_t, Args&&... args ); //C 17 起
/*构造一个包含 的对象,如同从参数 ilist, std::forward<Args>(args)...
直接初始化(但不是直接列表初始化) T 类型对象一般初始化。*/
template< class U, class... Args >
constexpr explicit optional( std::in_place_t,
std::initializer_list<U> ilist,
Args&&... args ); //C 17 起
/*构造一个包含的对象,如同从参数 std::forward<U>(value)
直接初始化(但不是直接列表初始化) T 类型对象一般初始化。*/
template < class U = T >
constexpr optional( U&& value ); //C 17 起 (条件性 explicit)
示例:
代码语言:javascript复制#include <iostream>
#include <optional>
#include <string>
int main()
{
std::optional<int> o1, // 空
o2 = 1, // 从右值初始化
o3 = o2; // 复制构造函数
// 调用 std::string( initializer_list<CharT> ) 构造函数
std::optional<std::string> o4(std::in_place, {'a', 'b', 'c'});
// 调用 std::string( size_type count, CharT ch ) 构造函数
std::optional<std::string> o5(std::in_place, 3, 'A');
// 从 std::string 移动构造,用推导指引拾取类型
std::optional o6(std::string{"deduction"});
std::cout << *o2 << ' ' << *o3 << ' ' << *o4 << ' ' << *o5 << ' ' << *o6 << 'n';
}
注:
std::in_place
是消除歧义的标签,其传递给ystd::optional
的构造函数,用来指示原位构造对象。
输出:
代码语言:javascript复制1 1 abc AAA deduction
析构函数
代码语言:javascript复制~optional(); //C 17 起, C 20 前
constexpr ~optional(); //C 20 起
operator=
代码语言:javascript复制//若 *this 在调用前含值,则通过调用其析构函数销毁所含值,如同用 value().T::~T() 。此调用后 *this 不含值。
optional& operator=( std::nullopt_t ) noexcept; //C 17 起, C 20 前
constexpr optional& operator=( std::nullopt_t ) noexcept; //C 20 起
//赋值 other 的状态
constexpr optional& operator=( const optional& other ); //C 17 起
//赋值 other 的状态
constexpr optional& operator=( optional&& other ) noexcept(); //C 17 起
/*完美转发赋值:取决于 *this 在调用前是否含值,从 std::forward<U>(value) 直接初始化,
或从 std::forward<U>(value) 赋值被含有值。*/
template< class U = T >
optional& operator=( U&& value ); //C 17 起, C 20 前
template< class U = T >
constexpr optional& operator=( U&& value ); //C 20 起
//赋值 other 的状态
template< class U >
optional& operator=( const optional<U>& other ); //C 17 起, C 20 前
template< class U >
constexpr optional& operator=( const optional<U>& other ); //C 20 起
//赋值 other 的状态
template< class U >
optional& operator=( optional<U>&& other ); //C 17 起, C 20 前
template< class U >
constexpr optional& operator=( optional<U>&& other ); //C 20 起
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
int main()
{
std::optional<const char*> s1 = "abc", s2; // 构造函数
s2 = s1; // 赋值
s1 = "def"; // 衰变赋值( U = char[4], T = const char* )
std::cout << *s2 << ' ' << *s1 << 'n';
}
输出:
代码语言:javascript复制abc def
观察器
访问所含值
optional
的std::optional<T>::operator->, std::optional<T>::operator*
函数主要的作用就是用来访问所含值,其原型如下:
//返回指向所含值的指针。
constexpr const T* operator->() const noexcept; //C 17 起
constexpr T* operator->() noexcept; //C 17 起
//返回到所含值的引用。
constexpr const T& operator*() const& noexcept; //C 17 起
constexpr T& operator*() & noexcept; //C 17 起
constexpr const T&& operator*() const&& noexcept; //C 17 起
constexpr T&& operator*() && noexcept; //C 17 起
注:若
*this
不含值则行为未定义。
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
#include <string>
int main()
{
using namespace std::string_literals;
std::optional<int> opt1 = 1;
std::cout<< "opt1: " << *opt1 << 'n';
*opt1 = 2;
std::cout<< "opt1: " << *opt1 << 'n';
std::optional<std::string> opt2 = "abc"s;
std::cout<< "opt2: " << *opt2 << " size: " << opt2->size() << 'n';
// 你能通过在到 optional 的右值上调用 operator* “取”其所含值
auto taken = *std::move(opt2);
std::cout << "taken: " << taken << " opt2: " << *opt2 << "size: " << opt2->size() << 'n';
}
输出:
代码语言:javascript复制opt1: 1
opt1: 2
opt2: abc size: 3
taken: abc opt2: size: 0
检查对象是否含值
std::optional<T>::operator bool, std::optional<T>::has_value
函数用来检查 *this
是否含值。若 *this
含值则为true
,若*this
不含值则 false
。 其函数原型为:
constexpr explicit operator bool() const noexcept; //C 17 起
constexpr bool has_value() const noexcept; //C 17 起
返回所含值
std::optional<T>::value
函数返回所含值,若 *this
含值,则返回到所含值引用。否则,抛出 std::bad_optional_access
异常。 其函数原型如下:
constexpr T& value() &;
constexpr const T& value() const&; //C 17 起
constexpr T&& value() &&;
constexpr const T&& value() const&&; //C 17 起
注:解引用运算符
operator*()
不检查此optional
是否含值,它可能比value()
更有效率。
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
int main()
{
std::optional<int> opt = {};
try {
int n = opt.value();
} catch(const std::exception& e) {
std::cout << e.what() << 'n';
}
}
输出:
代码语言:javascript复制bad optional access
在所含值可用时返回它,否则返回默认值
std::optional<T>::value_or
函数在*this
拥有值则返回其所含的值,否则返回 default_value
。其函数原型如下:
template< class U >
constexpr T value_or( U&& default_value ) const&; //C 17 起
template< class U >
constexpr T value_or( U&& default_value ) &&; //C 17 起
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
#include <cstdlib>
std::optional<const char*> maybe_getenv(const char* n)
{
if(const char* x = std::getenv(n))
return x;
else
return {};
}
int main()
{
std::cout << maybe_getenv("MYPWD").value_or("(none)") << 'n';
}
输出:
代码语言:javascript复制(none)
单子操作
and_then
std::optional<T>::and_then
在所含值存在时返回对其应用给定的函数的结果,否则返回空的optional
,其函数原型为:
template< class F >
constexpr auto and_then( F&& f ) &; //C 23 起
template< class F >
constexpr auto and_then( F&& f ) const&; //C 23 起
template< class F >
constexpr auto and_then( F&& f ) &&; //C 23 起
template< class F >
constexpr auto and_then( F&& f ) const&&; //C 23 起
若所含值存在则返回在其上调用 f 的结果。否则,返回返回类型的空值。 示例:
代码语言:javascript复制#include <iostream>
#include <optional>
std::optional<int> add5(int x)
{
return x 5;
}
std::optional<int> multiply2(int x)
{
return x * 2;
}
int main()
{
std::optional<int> x = 10;
auto y = x.and_then(add5).and_then(multiply2);
if (y)
{
std::cout << "Result: " << *y << std::endl;
}
else
{
std::cout << "Result is empty" << std::endl;
}
std::optional<int> z;
auto w = z.and_then(add5).and_then(multiply2);
if (w)
{
std::cout << "Result: " << *w << std::endl;
}
else
{
std::cout << "Result is empty" << std::endl; // Output: Result is empty
}
return 0;
}
输出:
代码语言:javascript复制Result: 30
Result is empty
transform
std::optional<T>::transform
函数在所含值存在时返回含有变换后的所含值的 optional
,否则返回空的 optional
,其函数原型如下:
template< class F >
constexpr auto transform( F&& f ) &; //C 23 起
template< class F >
constexpr auto transform( F&& f ) const&; //C 23 起
template< class F >
constexpr auto transform( F&& f ) &&; //C 23 起
template< class F >
constexpr auto transform( F&& f ) const&&; //C 23 起
若*this
含值则返回含有f
在所含值上调用结果的 std::optional
。否则返回这种类型的空 std::optional
。
示例:
代码语言:javascript复制#include <iostream>
#include <optional>
struct A { /* ... */ };
struct B { /* ... */ };
struct C { /* ... */ };
struct D { /* ... */ };
auto A_to_B(A) -> B { /* ... */ std::cout << "A => B n"; return {}; }
auto B_to_C(B) -> C { /* ... */ std::cout << "B => C n"; return {}; }
auto C_to_D(C) -> D { /* ... */ std::cout << "C => D n"; return {}; }
void try_transform_A_to_D(std::optional<A> o_A)
{
std::cout << (o_A ? "o_A has a valuen" : "o_A is emptyn");
std::optional<D> o_D = o_A.transform(A_to_B)
.transform(B_to_C)
.transform(C_to_D);
std::cout << (o_D ? "o_D has a valuenn" : "o_D is emptynn");
};
int main()
{
try_transform_A_to_D( A{} );
try_transform_A_to_D( {} );
}
输出:
代码语言:javascript复制o_A has a value
A => B
B => C
C => D
o_D has a value
o_A is empty
o_D is empty
or_else
std::optional<T>::or_else
函数在 optional
含值时返回自身,否则返回给定函数的结果。其函数原型如下:
template< class F >
constexpr optional or_else( F&& f ) const&; //C 23 起
template< class F >
constexpr optional or_else( F&& f ) &&; //C 23 起
若 *this
含值则返回它。否则返回 f
的结果。
修改器
swap
代码语言:javascript复制void swap( optional& other ) noexcept(/* see below */); //C 17 起, C 20 前
constexpr void swap( optional& other ) noexcept(/* see below */); //C 20 起
std::optional<T>::swap
用来与 other 交换内容。
示例:
代码语言:javascript复制#include <iostream>
#include <string>
#include <optional>
int main()
{
std::optional<std::string> opt1("First example text");
std::optional<std::string> opt2("2nd text");
enum Swap { Before, After };
auto print_opts = [&](Swap e) {
std::cout << (e == Before ? "Before swap:n" : "After swap:n");
std::cout << "opt1 contains '" << opt1.value_or("") << "'n";
std::cout << "opt2 contains '" << opt2.value_or("") << "'n";
std::cout << (e == Before ? "---SWAP---n": "n");
};
print_opts(Before);
opt1.swap(opt2);
print_opts(After);
// 在仅一者含值时交换
opt1 = "Lorem ipsum dolor sit amet, consectetur tincidunt.";
opt2.reset();
print_opts(Before);
opt1.swap(opt2);
print_opts(After);
}
输出:
代码语言:javascript复制Before swap:
opt1 contains 'First example text'
opt2 contains '2nd text'
---SWAP---
After swap:
opt1 contains '2nd text'
opt2 contains 'First example text'
Before swap:
opt1 contains 'Lorem ipsum dolor sit amet, consectetur tincidunt.'
opt2 contains ''
---SWAP---
After swap:
opt1 contains ''
opt2 contains 'Lorem ipsum dolor sit amet, consectetur tincidunt.'
reset
代码语言:javascript复制void reset() noexcept; //C 17 起, C 20 前
constexpr void reset() noexcept; //C 20 起
std::optional<T>::reset
用来销毁任何所含值,若 *this
含值,则如同用value().T::~T()
销毁此值。否则无效果。*this
在此调用后不含值。
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
struct A {
std::string s;
A(std::string str) : s(std::move(str)) { std::cout << " constructedn"; }
~A() { std::cout << " destructedn"; }
A(const A& o) : s(o.s) { std::cout << " copy constructedn"; }
A(A&& o) : s(std::move(o.s)) { std::cout << " move constructedn"; }
A& operator=(const A& other) {
s = other.s;
std::cout << " copy assignedn";
return *this;
}
A& operator=(A&& other) {
s = std::move(other.s);
std::cout << " move assignedn";
return *this;
}
};
int main()
{
std::cout << "Create empty optional:n";
std::optional<A> opt;
std::cout << "Construct and assign value:n";
opt = A("Lorem ipsum dolor sit amet, consectetur adipiscing elit nec.");
std::cout << "Reset optional:n";
opt.reset();
std::cout << "End examplen";
}
输出:
代码语言:javascript复制Create empty optional:
Construct and assign value:
constructed
move constructed
destructed
Reset optional:
destructed
End example
emplace
std::optional<T>::emplace
用来原位构造所含值 ,其函数原型如下:
//以 std::forward<Args>(args)... 为参数直接初始化(但不是直接列表初始化)所含值。
template< class... Args >
T& emplace( Args&&... args ); //C 17 起, C 20 前
template< class... Args >
constexpr T& emplace( Args&&... args ); //C 20 起
//以 ilist, std::forward<Args>(args)... 为参数直接初始化(但不是直接列表初始化)所含值。
template< class U, class... Args >
T& emplace( std::initializer_list<U> ilist, Args&&... args ); //C 17 起, C 20 前
template< class U, class... Args >
constexpr T& emplace( std::initializer_list<U> ilist, Args&&... args ); //C 20 起
若*this
已在此调用前含值,则调用其析构函数销毁所含值。
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
struct A {
std::string s;
A(std::string str) : s(std::move(str)) { std::cout << " constructedn"; }
~A() { std::cout << " destructedn"; }
A(const A& o) : s(o.s) { std::cout << " copy constructedn"; }
A(A&& o) : s(std::move(o.s)) { std::cout << " move constructedn"; }
A& operator=(const A& other) {
s = other.s;
std::cout << " copy assignedn";
return *this;
}
A& operator=(A&& other) {
s = std::move(other.s);
std::cout << " move assignedn";
return *this;
}
};
int main()
{
std::optional<A> opt;
std::cout << "Assign:n";
opt = A("Lorem ipsum dolor sit amet, consectetur adipiscing elit nec.");
std::cout << "Emplace:n";
// 由于 opt 含值,这亦将销毁该值
opt.emplace("Lorem ipsum dolor sit amet, consectetur efficitur. ");
std::cout << "End examplen";
}
输出:
代码语言:javascript复制Assign:
constructed
move constructed
destructed
Emplace:
destructed
constructed
End example
destructed
非成员函数
比较 optional 对象
operator==, !=, <, <=, >, >=, <=>(std::optional)
函数用来比较optional
对象。
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
int main()
{
std::optional<int> oEmpty;
std::optional<int> oTwo(2);
std::optional<int> oTen(10);
std::cout << std::boolalpha;
std::cout << (oTen > oTwo) << "n";
std::cout << (oTen < oTwo) << "n";
std::cout << (oEmpty < oTwo) << "n";
std::cout << (oEmpty == std::nullopt) << "n";
std::cout << (oTen == 10) << "n";
}
输出:
代码语言:javascript复制true
false
true
true
true
make_optional
std::make_optional
函数用来创建一个optional
对象 。其函数原型如下:
//从value 创建 optional 对象。
template< class T >
constexpr std::optional<std::decay_t<T>> make_optional( T&& value ); //C 17 起
//从 args... 创建原位构造的 optional 对象。
template< class T, class... Args >
constexpr std::optional<T> make_optional( Args&&... args ); //C 17 起
//从 il 和 args... 创建原位构造的 optional 对象。
template< class T, class U, class... Args >
constexpr std::optional<T> make_optional( std::initializer_list<U> il, Args&&... args ); //C 17 起
示例:
代码语言:javascript复制#include <optional>
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
int main()
{
auto op1 = std::make_optional<std::vector<char>>({'a','b','c'});
std::cout << "op1: ";
for (char c: op1.value()){
std::cout << c << ",";
}
auto op2 = std::make_optional<std::vector<int>>(5, 2);
std::cout << "nop2: ";
for (int i: *op2){
std::cout << i << ",";
}
std::string str{"hello world"};
auto op3 = std::make_optional<std::string>(std::move(str));
std::cout << "nop3: " << quoted(op3.value_or("empty value")) << 'n';
std::cout << "str: " << std::quoted(str) << 'n';
}
输出:
代码语言:javascript复制op1: a,b,c,
op2: 2,2,2,2,2,
op3: "hello world"
str:
std::swap(std::optional)
std::swap(std::optional)
是特化 std::swap
算法 ,其函数原型如下:
template< class T >
void swap( std::optional<T>& lhs,
std::optional<T>& rhs ) noexcept(/* see below */); //C 17 起, C 20 前
template< class T >
constexpr void swap( std::optional<T>& lhs,
std::optional<T>& rhs ) noexcept(); //C 20 起
对std::optional
重载 std::swap
算法。交换lhs
与 rhs
的状态。等效地调用 lhs.swap(rhs)
。 示例:
#include <iostream>
#include <optional>
#include <string>
int main()
{
std::optional<std::string> a{"██████"}, b{"▒▒▒▒▒▒"};
auto print = [&](auto const& s) {
std::cout
<< s << "t"
<< "a = " << a.value_or("(null)") << " "
<< "b = " << b.value_or("(null)") << 'n';
};
print("Initially:");
std::swap(a, b);
print("swap(a, b):");
a.reset();
print("n""a.reset():");
std::swap(a, b);
print("swap(a, b):");
}
输出:
代码语言:javascript复制Initially: a = ██████ b = ▒▒▒▒▒▒
swap(a, b): a = ▒▒▒▒▒▒ b = ██████
a.reset(): a = (null) b = ██████
swap(a, b): a = ██████ b = (null)
何时使用
通常在以下情况下会用到std::optional
:
- 当我们想很好地表示一个可能会为空的类型的时候。
- 函数返回一些处理结果,该结果无法生成值,但该结果并不是错误。
- 执行资源的延时加载。
- 将可选参数传递到函数中。
使用示例
函数返回 std::optional
如果从函数返回可选值,则仅 std::nullopt 返回或计算值非常方便。
代码语言:javascript复制std::optional<std::string> TryParse(Input input)
{
if (input.valid())
return input.asString();
return std::nullopt;
}
延时初始化
在某个类初始化的时候,由于某种原因,其某个成员还不能被初始化,也就是说该类初始化的时候需要选择性的初始化它的成员,其某个成员需要在稍晚时间或者在发生某个动作后才能够被初始化。如果我们要实现这种功能,使用optional
会非常方便:
using T = /* some type */;
struct S {
optional<T> maybe_T;
void construct_the_T(int arg) {
// 我们无需处理重复构造所带来的问题,因为
// optional的emplace member会自动销毁所
// 之前存在的对象并构建一个新对象。
maybe_T.emplace(arg);
}
T& get_the_T() {
assert(maybe_T);
return maybe_T.value();
}
// ... 接下来的拷贝和构造函数就能保证问题不会处在optional这里啦!...
};
optional
特别适合延迟初始化问题,因为它本身就是延迟初始化的实例。所包含 T
的内容可以在构造时初始化,也可以在以后的某个时间初始化,或者永远不会初始化。任何包含 T
的内容都必须在销毁时 optional
销毁。
作为可选参数
代码语言:javascript复制#include <optional>
#include <iostream>
class UserRecord
{
public:
UserRecord (const std::string& name, std::optional<std::string> nick, std::optional<int> age)
: mName{name}, mNick{nick}, mAge{age}
{
}
friend std::ostream& operator << (std::ostream& stream, const UserRecord& user);
private:
std::string mName;
std::optional<std::string> mNick;
std::optional<int> mAge;
};
std::ostream& operator << (std::ostream& os, const UserRecord& user)
{
os << user.mName << ' ';
if (user.mNick) {
os << *user.mNick << ' ';
}
if (user.mAge)
os << "age of " << *user.mAge;
return os;
}
int main()
{
UserRecord tim { "Tim", "SuperTim", 16 };
UserRecord nano { "Nathan", std::nullopt, std::nullopt };
std::cout << tim << "n";
std::cout << nano << "n";
}
总结
对于std::optional
我们注意以下几点:
std::optional
是表示“可为空”类型的包装器类型。std::optional
不会使用任何动态分配。std::optional
包含值或为空。- 使用
operator *
、operator->
或value() value_or()
访问基础值。
- 使用
std::optional
隐式转换为bool
,以便我们可以轻松检查它是否包含值。
当我们需要一个具有延迟初始化的对象、或者用来表达value
或no value
的时候,我们可以使用std::optional
来将使用其类型提高抽象的级别,使其他人更加容易理解我们大代码在做什么,因为声明 optional<T> f();
和void g(optional<T>);
表达意图比做 pair<T, bool> f();
或 void g(T t, bool is_valid);
更清晰简洁。