std::optional (可选类型)
std::optional 是 C++17 引入的一个“包装器”类型(在
头文件中)。它解决了一个在 C++
中存在已久的古老问题:一个函数或变量如何表示它“可能没有值”? >
包装器类型指的是一种数据结构,它可以“包装”一个值,使其具有额外的语义或功能。
它的设计理念是:通过将一个类型 T 包装在 std::optional
中,可以明确地表示“这个值可能存在,也可能不存在”。这比使用裸指针(如
T*)或特殊的哨兵值(如 -1 或 nullptr)更安全、更直观。
std::optional 简单地封装了一个 T 类型的对象和一个
bool 标记。
- 如果它包含值,你可以像普通 T 一样访问它。
- 如果它不包含值(即“为空”),它就是 std::nullopt
状态。
它在语义上清晰地表达了:“我是一个
T,或者我什么都不是”。并且在内部进行优化:它的大小通常就是
sizeof(T) 加上一个
bool(以及一些对齐字节)。它不会在堆上分配内存(除非 T
自己这么做)。
其函数接口包括: - 构造函数: -
std::optional<T> opt; // 默认构造,表示“空”状态 -
std::optional<T> opt(value); // 构造时提供一个 T
类型的值 - 检查状态: - bool has_value() const; //
检查是否包含值 -
explicit operator bool() const; // 可以作为 bool
使用,表示是否有值, 例如 if(opt) {...} - 访问值:
- T& value(); // 返回对包含值的引用,如果为空则抛出异常
- const T& value() const; // 常量版本 -
T& operator*(); //
解引用操作符,返回对值的引用(不检查是否有值) -
const T& operator*() const; // 常量版本 -
T* operator->(); //
指针访问操作符,返回指向值的指针(不检查是否有值) -
const T* operator->() const; // 常量版本 -
其他有用的方法: -
T value_or(const T& default_value) const; //
如果有值则返回值,否则返回提供的默认值 - void reset(); //
重置为“空”状态
下面是一个使用 std::optional 的简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <iostream> #include <optional> #include <string> #include <map>
std::optional<std::string> find_username(int user_id, const std::map<int, std::string>& db) { auto it = db.find(user_id); if (it == db.end()) { return std::nullopt; } return it->second; }
int main() { std::map<int, std::string> user_database = { {101, "Alice"}, {102, "Bob"} };
std::optional<std::string> user_101 = find_username(101, user_database);
if (user_101.has_value()) { std::cout << "ID 101: " << user_101.value() << std::endl; } if (user_101) { std::cout << "ID 101: " << *user_101 << std::endl; }
std::optional<std::string> user_103 = find_username(103, user_database);
if (!user_103) { std::cout << "ID 103: Not found." << std::endl; }
std::string user_103_name = user_103.value_or("Guest"); std::cout << "ID 103 login as: " << user_103_name << std::endl; try { std::cout << user_103.value() << std::endl; } catch (const std::exception& e) { std::cout << "Error: " << e.what() << std::endl; } }
|
std::variant<Ts…> (变体类型)
std::optional 是 T 或 空,而 std::variant 是 T 或 U 或
V…。它们共同构成了现代 C++ 中强大的代数数据类型工具。
std::variant<T, U, ...> 是 C++17
引入的一个模板类(在 头文件中),它代表一个“类型安全的
union”。一个 variant 对象在任何时刻都只能持有其模板参数列表(T,
U, …)中某一种类型的值。
回忆一下传统的 union:
union(共用体)就是几个成员共享同一块内存。写入一个成员会覆盖其他成员的数据,
在同一时刻,只有最后一次写入的成员是“有效”的. 也就是说, union
的目的就是节省内存。它让多个可能互斥使用的变量共用同一块存储空间。
1 2 3 4 5 6 7 8 9 10 11 12
| union U { int i; float f; char c; };
U u; u.i = 42; u.f = 3.14f; printf("%d\n", u.i);
|
而这种实现是有缺陷的:
- 类型不安全:C++ 编译器(或运行时)不知道 union 当前存储的是 int 还是
float。如果你存入一个 int,却试图按 float
读出来,这是未定义行为 (Undefined
Behavior)。你需要一个额外的变量(如
enum)来手动跟踪当前激活的成员。
- 限制严格:不能持有非平凡(non-POD)类型,比如
std::string 或 std::vector(因为不知道该调用哪个析构函数)。
std::variant 完美地解决了上述问题:它在语义上表达了:“我的值要么是
T,要么是 U,要么是…”。
- 类型安全:variant
始终知道它当前持有的是哪种类型。你不能错误地访问它(访问前必须检查,或者使用安全的
std::get_if)。
- 支持复杂类型:它可以安全地持有 std::string、std::vector
等,因为它知道在销毁或切换类型时该调用哪个析构函数。
函数接口包括: - 构造函数: -
std::variant<Ts...> var; //
默认构造,持有第一个类型的默认值 -
std::variant<Ts...> var(value); //
构造时提供某个类型的值 - 检查当前类型: -
std::size_t index() const; //
返回当前持有的类型在模板参数列表中的索引 -
bool holds_alternative<T>() const; //
检查当前是否持有类型 T -
std::variant_npos:一个常量,表示“无效索引”。 - 访问值: -
T& std::get<T>(var); // 返回对持有的 T
类型值的引用,如果当前不是 T 则抛出异常 -
const T& std::get<T>(const var); // 常量版本 -
T* std::get_if<T>(&var); // 返回指向 T
类型值的指针,如果当前不是 T 则返回 nullptr -
const T* std::get_if<T>(const &var); //
常量版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include <iostream> #include <variant> #include <string>
using HttpResult = std::variant<int, std::string>;
HttpResult fetch_data(bool success) { if (success) { return "Here is your data!"; } else { return 404; } }
int main() { HttpResult result_ok = fetch_data(true); HttpResult result_fail = fetch_data(false);
if (std::holds_alternative<std::string>(result_ok)) { std::cout << "Success: " << std::get<std::string>(result_ok) << std::endl; } if (std::holds_alternative<int>(result_fail)) { std::cout << "Failure Code: " << std::get<int>(result_fail) << std::endl; }
std::cout << "Failure Index: " << result_fail.index() << std::endl;
try { std::get<std::string>(result_fail); } catch (const std::exception& e) { std::cout << "Error: " << e.what() << std::endl; }
if (int* code = std::get_if<int>(&result_fail)) { std::cout << "Safe get failure: " << *code << std::endl; } if (std::string* data = std::get_if<std::string>(&result_ok)) { std::cout << "Safe get success: " << *data << std::endl; }
std::cout << "Visiting result_ok: "; std::visit([](const auto& value) { using T = std.decay_t<decltype(value)>; if constexpr (std::is_same_v<T, int>) { std::cout << "It's an int: " << value << std::endl; } else if constexpr (std::is_same_v<T, std::string>) { std::cout << "It's a string: " << value << std::endl; } }, result_ok); std::cout << "Visiting result_fail: "; std::visit([](const auto& value) { if constexpr (std::is_same_v<T, int>) { std::cout << "It's an int: " << value << std::endl; } else if constexpr (std::is_same_v<T, std::string>) { std::cout << "It's a string: " << value << std::endl; } }, result_fail); }
|
std::string_view:零拷贝的字符串“视图”
std::string_view(在 头文件中)是一个 C++17 引入的只读
(read-only) 视图类,它提供了对已存在的字符串数据的非拥有 (non-owning)
引用。
简单来说:它不是一个字符串,它只是指向某处字符串的“窗口”。它是一个轻量级的对象(通常只有
16 字节:一个 const char* 指针和一个
size_t 长度)。
- 当它从 std::string 构造时,它不拷贝字符串数据,它只是指向
std::string 内部的数据缓冲区。
- 当它从 const char* 构造时,它不拷贝字符串数据,它只是指向那个 C
风格字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <iostream> #include <string_view> #include <string>
void print_string(std::string_view sv) { std::cout << sv << std::endl; }
int main() { print_string("Hello, World!");
std::string s1 = "Hello, std::string!"; print_string(s1);
std::string large_message = "HEADER:Some very large data payload..."; std::string_view header_view = large_message; print_string(header_view.substr(0, 6)); print_string(std::string_view(large_message.data(), 6)); }
|
并且, std::string_view 提供了与 std::string 几乎一致的只读
API,例如:
- size(), length(), empty()
- data()
- operator[], front(), back()
- substr(), remove_prefix(), remove_suffix()
- find(), rfind(), find_first_of() 等。
不过它没有所有修改字符串的 API(如 push_back, append, clear)。
结构化绑定 (Structured
Bindings)
结构化绑定是 C++17
引入的一种语法,它允许你用一个声明来解包一个“复合”对象(如
pair、tuple、struct 或
array)的成员,并将它们绑定到多个新的局部变量上。
它的核心语法是:auto [var1, var2, ...] = object;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <iostream> #include <tuple> #include <map> int main() { std::map<std::string, int> age_map = { {"Alice", 30}, {"Bob", 25} };
for (const auto& [name, age] : age_map) { std::cout << name << " is " << age << " years old." << std::endl; }
std::tuple<std::string, int, double> person = {"Charlie", 28, 75.5};
auto [person_name, person_age, person_weight] = person; std::cout << person_name << " is " << person_age << " years old and weighs " << person_weight << " kg." << std::endl;
struct Point { int x; int y; };
Point p = {10, 20}; auto [px, py] = p; std::cout << "Point coordinates: (" << px << ", " << py << ")" << std::endl; }
|
if constexpr (编译时条件判断)
已有介绍, 不再赘述