相关文章系列
目录
1.概述
C++17的三剑客分别是std::optional, std::any, std::vairant。今天主要讲std::optional。它是一个类模版,在头文件<
- template< class T >
- class optional;
类模板 std::optional
管理一个可选 的容纳值,既可以存在也可以不存在的值。
一种常见的 optional
使用情况是一个可能失败的函数的返回值。与其他手段,如 std::pairoptional
良好地处理构造开销高昂的对象,并更加可读,因为它显式表达意图。
以下是一个简单的代码示例,展示了如何在C++程序中引入和使用std::optional:
- #include
// 引入std::optional - #include
-
- std::optional<double> getValue(bool r) {
- if (r) {
- return 1.52;
- } else {
- return std::nullopt;
- }
- }
-
- int main() {
- auto value = getValue(true);
- if (value.has_value()) {
- std::cout << "Value: " << *value << std::endl;
- } else {
- std::cout << "No value" << std::endl;
- }
-
- return 0;
- }
在这个代码示例中,我们首先引入了
2.构建方式
std::optional的构建方式主要有:
2.1.默认构造
std::optional的默认构造函数创建一个不包含值的std::optional对象。这在你需要延迟初始化或者表示一个可能不存在的值时非常有用。
std::optional<double> opt; // 创建一个不包含值的std::optional对象
2.2.移动构造
移动构造又称右值构造。你可以通过提供一个右值来构造std::optional对象。这个值将被复制或移动到新创建的std::optional对象中。如下示例:
- std::optional<double> a(10.09); // 创建一个包含值10.09的std::optional对象
- std::optional<double> b(std::move(a)); //使用移动构造函数创建一个新的std::optional对象
2.3.拷贝构造
如下示例:
- std::optional<int> a(22222);
- std::optional<int> b(a); // 使用拷贝构造函数创建一个新的std::optional对象
2.4.std::in_place_t构造
std::optional类还提供了in-place构造函数,允许你在std::optional对象的存储空间中直接构造值,避免了不必要的拷贝或移动操作。std::in_place
是消除歧义的标签,其传递给ystd::optional
的构造函数,用来指示原位构造对象。示例如下:
- #include
- #include
- #include
-
- int main()
- {
- // 调用 std::string( initializer_list
) 构造函数 - std::optional
o4(std::in_place, {'a', 'b', 'c'}) ; -
- // 调用 std::string( size_type count, CharT ch ) 构造函数
- std::optional
o5(std::in_place, 3, 'A') ; -
- // 从 std::string 移动构造,用推导指引拾取类型
-
- std::optional o6(std::string{"deduction"});
-
- std::cout << *o2 << ' ' << *o3 << ' ' << *o4 << ' ' << *o5 << ' ' << *o6 << '\n';
- }
输出:1 1 abc AAA deduction
2.5.std::make_optional构造
创建一个包含给定值的std::optional对象,它的定义如下:
从定义看出,可以从右值,可变参数和std::initializer_list等多种方式用std::make_optional构造出std::optional对象。示例如下:
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- auto op1 = std::make_optional
char>>({'a','b','c'}); - std::cout << "op1: ";
- for (char c: op1.value()){
- std::cout << c << ",";
- }
- auto op2 = std::make_optional
int>>(5, 2); - std::cout << "\nop2: ";
- for (int i: *op2){
- std::cout << i << ",";
- }
- std::string str{"hello world"};
- auto op3 = std::make_optional
(std::move(str)); - std::cout << "\nop3: " << quoted(op3.value_or("empty value")) << '\n';
- std::cout << "str: " << std::quoted(str) << '\n';
- }
输出:
- op1: a,b,c,
- op2: 2,2,2,2,2,
- op3: "hello world"
- str:
3.修改器
3.1.operator=
对内容赋值,重载operator=操作符的类型有:
从中看出std::optional的赋值函数参数包括std::nullopt_t、左值引用、右值引用、模板单值、模板做值和模板右值。示例如下:
- #include
- #include
- int main()
- {
- std::optional<const char*> s1 = "abc", s2; // 构造函数
- s2 = s1; // 赋值
- s1 = "def"; // 衰变赋值( U = char[4], T = const char* )
- std::cout << *s2 << ' ' << *s1 << '\n';
- }
输出:abc def
3.2.emplace
emplace的定义如下:
示例如下:
- #include
- #include
-
- struct A {
- std::string s;
- A(std::string str) : s(std::move(str)) { std::cout << " constructed\n"; }
- ~A() { std::cout << " destructed\n"; }
- A(const A& o) : s(o.s) { std::cout << " copy constructed\n"; }
- A(A&& o) : s(std::move(o.s)) { std::cout << " move constructed\n"; }
- A& operator=(const A& other) {
- s = other.s;
- std::cout << " copy assigned\n";
- return *this;
- }
- A& operator=(A&& other) {
- s = std::move(other.s);
- std::cout << " move assigned\n";
- return *this;
- }
- };
-
- int main()
- {
- std::optional<int> a;
- a.emplace(10); // 在optional对象中就地构造一个值
-
- std::optional 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 example\n";
- }
输出:
- Assign:
- constructed
- move constructed
- destructed
- Emplace:
- destructed
- constructed
- End example
- destructed
3.3.reset
重置对象,若 std::optional 含值,则如同用 value().T::~T() 销毁此值。否则无效果。示例如下:
- #include
- #include
-
- struct A {
- std::string s;
- A(std::string str) : s(std::move(str)) { std::cout << " constructed\n"; }
- ~A() { std::cout << " destructed\n"; }
- A(const A& o) : s(o.s) { std::cout << " copy constructed\n"; }
- A(A&& o) : s(std::move(o.s)) { std::cout << " move constructed\n"; }
- A& operator=(const A& other) {
- s = other.s;
- std::cout << " copy assigned\n";
- return *this;
- }
- A& operator=(A&& other) {
- s = std::move(other.s);
- std::cout << " move assigned\n";
- return *this;
- }
- };
-
- int main()
- {
- std::cout << "Create empty optional:\n";
- std::optional 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 example\n";
- }
输出:
- Create empty optional:
- Construct and assign value:
- constructed
- move constructed
- destructed
- Reset optional:
- destructed
- End example
3.4.swap
交换内容,如果内部有值,这先析构内部值,再交换值。示例如下:
- #include
- #include
- #include
-
- int main()
- {
- std::optional
opt1("First example text") ; - std::optional
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);
- }
输出:
- 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.'
4.观察器
4.1.operator->和operator*
operator->返回所含值的指针;operator*返回所函数的引用,
此运算符不检查 std::
optional
是否含值!你能手动用 has_value() 或简单地用 operator bool() 做检查。另外,若需要有检查访问,可使用 value() 或 value_or() 。
示例如下:
- #include
- #include
- #include
-
- 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
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';
- }
输出:
- opt1: 1
- opt1: 2
- opt2: abc size: 3
- taken: abc opt2: size: 0
4.2.operator bool和has_value
检查std::optional是否函数,这个比较简单,这里就不赘述了。
4.3.value
若 std::optional含值,则返回到所含值引用,示例如下:
- #include
- #include
- int main()
- {
- std::optional<int> opt = {};
-
- try {
- int n = opt.value();
- } catch(const std::exception& e) {
- std::cout << e.what() << '\n';
- }
- }
输出:bad optional access
4.4.value_or
value_or的定义如下:
若std::optional 拥有值则返回其所含的值,否则返回 default_value
。
1) 等价于 bool(*this) ? **this : static_cast
2) 等价于 bool(*this) ? std::move(**this) : static_cast
示例如下:
- #include
- #include
- #include
-
- 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';
- }
输出:(none)
5.单子操作(C++23起)
5.1.and_then
在所含值存在时返回对其应用给定的函数的结果,否则返回空的 std::optional。下面示例可能包括C++23的部分内容,不清楚的地方可以去查询相关文档。代码如下:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- std::optional<int> to_int(std::string_view sv)
- {
- int r {};
- auto [ptr, ec] { std::from_chars(sv.data(), sv.data() + sv.size(), r) };
- if (ec == std::errc())
- return r;
- else
- return std::nullopt;
- }
-
- int main()
- {
- using namespace std::literals;
-
- const std::vector
> v - {
- "1234", "15 foo", "bar", "42", "5000000000", " 5", std::nullopt, "-43"
- };
-
- for (auto&& x : v | std::views::transform(
- [](auto&& o)
- {
- // 调试打印输入的 optional
的内容 - std::cout << std::left << std::setw(13)
- << std::quoted(o.value_or("nullopt")) << " -> ";
-
- return o
- // 若 optional 为空则转换它为持有 "" 字符串的 optional
- .or_else([]{ return std::optional{""s}; })
- // 拉平映射 string 为 int (失败时产生空的 optional)
- .and_then(to_int)
- // 映射 int 为 int + 1
- .transform([](int n) { return n + 1; })
- // 转换回 string
- .transform([](int n) { return std::to_string(n); })
- // 以 and_than 替换,并用 "NaN" 变换并忽略所有剩余的空 optional
- // and_then and ignored by transforms with "NaN"
- .value_or("NaN"s);
- }))
- std::cout << x << '\n';
- }
输出:
- "1234" -> 1235
- "15 foo" -> 16
- "bar" -> NaN
- "42" -> 43
- "5000000000" -> NaN
- " 5" -> NaN
- "nullopt" -> NaN
- "-43" -> -42
5.2.transform
在所含值存在时返回含有变换后的所含值的 std::optional
,否则返回空的 std::optional。示例如下:
- #include
- #include
-
- 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 o_A)
- {
- std::cout << (o_A ? "o_A has a value\n" : "o_A is empty\n");
-
- std::optional
o_D = o_A.transform(A_to_B) - .transform(B_to_C)
- .transform(C_to_D);
-
- std::cout << (o_D ? "o_D has a value\n\n" : "o_D is empty\n\n");
- };
-
- int main()
- {
- try_transform_A_to_D( A{} );
- try_transform_A_to_D( {} );
- }
输出:
- o_A has a value
- A => B
- B => C
- C => D
- o_D has a value
-
- o_A is empty
- o_D is empty
5.3.or_else
在 std::optional
含值时返回自身,否则返回给定函数的结果。功能比较简单,在这里就不在赘述了。
6.使用场景
函数返回值:当函数可能返回一个值,也可能不返回值时,可以使用std:optional作为返回类型。这种方式可以避免使用指针或特殊值来表示无值的情况,从而提高代码的简洁性和安全性。
参数传递:将std::optional作为数参数,可以接受或忽略该参数的值。这种方式可以使函数更加灵活,适应不同的情况。
容器类:可以使用std:optional作为容器类(如std:vector、std:list等)的元素类型,以存储可能不存在的值。这种方式可以方便地处理容器中的空值,而无需使用指针或特殊值。
可选状态:当某个对象可能处于某种状态,也可能不处于该状态时,可以使用std:optional来表示该状态。例如,一个购物车可能包含一个可选的运费,可以使用std::optional
来表示是否计算运费。
异步编程:在异步编程中std:optional可以用于表示异步操作的结果。例如,一个异步函数可能返回一个std::optional
表示异步计算的结果可能是一个整数值,或者没有结果(空值)。
7.注意事项
在使用C++的std::optional类时,有一些重要的注意事项需要我们了解。这些注意事项可以帮助我们更好地理解和使用std::optional类,避免在编程中出现错误。
7.1.std::optional未初始化去访问
当我们创建一个std::optional对象但没有给它赋值时,这个对象就处于未初始化的状态。在这种状态下,如果我们试图访问它的值,就会抛出std::bad_optional_access异常。
- std::optional<int> a;
- try {
- int value = a.value(); // 抛出std::bad_optional_access
- } catch (const std::bad_optional_access& e) {
- std::cout << e.what() << '\n';
- }
在这个例子中,我们创建了一个未初始化的std::optional对象,并试图访问它的值。这会抛出一个std::bad_optional_access异常,我们可以捕获这个异常并处理它。
在实际编程中,我们应该在访问std::optional的值之前,先使用has_value()函数或者bool运算符检查它是否已经被初始化。
- std::optional<int> a;
- if (a) { // 或者 if (a.has_value())
- int value = a.value();
- }
7.2.std::optional的比较操作
std::optional支持所有的比较操作,包括==, !=, <, <=, >, >=。这些比较操作首先会比较两个std::optional对象的初始化状态,然后再比较它们的值。
- std::optional<int> a= 1;
- std::optional<int> b= 2;
- std::optional<int> b;
-
- std::cout << (a == b) << '\n'; // 输出0,因为a和b的值不相等
- std::cout << (a == c) << '\n'; // 输出0,因为a已经初始化,而c未初始化
- std::cout << (c == std::nullopt) << '\n'; // 输出1,因为c未初始化
在这个例子中,我们创建了两个已经初始化的std::optional对象和一个未初始化的std::optional对象,然后比较它们的值和初始化状态。
7.3.std::optional的生命周期
std::optional的生命周期和它包含的值的生命周期是一致的。当std::optional被销毁时,它包含的值也会被销毁。这意味着我们不能返回一个包含局部变量的std::optional。
- std::optional
getName(bool c) { - std::string name = "zdxiao";
- if (c) {
- return name; // 错误:返回一个包含局部变量的std::optional
- }
- return std::nullopt;
在这个例子中,我们试图返回一个包含局部变量name的std::optional
在实际编程中,我们应该避免返回包含局部变量的std::optional。我们可以返回一个值,或者返回std::nullopt_t表示没有值。
- std::optional
getName(bool c) { - if (c) {
- return "zdxiao"; // 正确:返回一个值
- }
- return std::nullopt; // 正确:表示没有值
- }
在这个修改后的例子中,我们返回一个字符串字面量,而不是一个局部变量。这样返回的std::optionalstd::string就会包含一个有效的值。
以上就是在使用std::optional类时需要注意的一些重要事项。在实际编程中,我们应该充分理解和掌握这些注意事项,以避免在编程中出现错误。
8.总结
上面全面讲解了std::optional的用法和一些注意事项,要想深入理解它,那就需要在平时的工作中慢慢的去使用它,细细体会,才能真正领会发明std::optional的意义。
评论记录:
回复评论: