本期我们就来介绍一下C++17中新增的标准库的接口
相关代码上传到gitee:楼田莉子/Linux学习
目录
构造与赋值 (Construction & Assignment)
修改器与工具函数 (Modifiers & Utility Functions)
optional
官方文档:Standard library header <optional> (C++17) - cppreference.com
<optional> 是C++17引入的一个关键头文件,它提供了一个名为 std::optional 的类模板,主要用于表达一个值"可能存在,也可能不存在"的语义。常用于替代传统的指针或特殊值(如-1)来表示函数的返回值可能无效,或者类中的某个成员变量可能未被设置的情况
| 类别 | 接口名称 | 功能描述 |
|---|---|---|
| 主类模板 | std::optional<T> |
核心类,管理一个可能存在的 T 类型值。 |
| 空状态 | std::nullopt_t / std::nullopt |
std::nullopt 是一个常量,用来显式地将 optional 对象置为空。 |
| 异常类 | std::bad_optional_access |
当对一个空的 optional 调用 .value() 时,会抛出此异常。 |
接口
std::optional 的大部分操作都围绕"构造-检查-访问-修改"这个生命周期展开。
构造与赋值
提供了丰富的构造方式,支持值初始化、原位构造和柯里化构造。
-
constexpr optional(): 默认构造,对象为空。 -
constexpr optional(std::nullopt_t): 显式构造为空。 -
constexpr optional(const T& value): 通过拷贝/移动直接包含一个值。 -
template<...> constexpr explicit optional(std::in_place_t, Args&&...): 使用std::in_place进行原位构造,直接在内部构建对象,避免临时对象拷贝,效率更高。 -
template<...> constexpr optional& operator=(U&& value): 通过赋值操作符包含新值,会正确析构旧值。
状态观测:询问"有值吗?"
在访问值之前,必须先检查其存在性。if (opt) 是C++工程师最常用的快捷方式。
-
constexpr explicit operator bool() const noexcept: 提供bool语境转换,允许if (opt)的简洁写法。 -
constexpr bool has_value() const noexcept: 显式查询,if (opt.has_value())语义更明确。
值访问:安全地获取数据
这些方法提供了不同安全级别的访问,体现了"先检查,后使用"的设计哲学。
-
constexpr T& operator*()/constexpr const T* operator->(): 类似指针的解引用。务必确保对象非空,否则行为未定义。 -
constexpr T& value(): 安全访问。若对象为空,会抛出std::bad_optional_access异常,适合RAII风格。 -
template<...> constexpr T value_or(U&& default_value): 提供带默认值的安全访问。若对象为空则返回默认值,非常适合容错场景。
内容修改
对已存在的值进行原地修改,避免额外的构造开销。
-
constexpr void reset() noexcept: 清空对象,销毁内部值。 -
template<...> constexpr T& emplace(Args&&...): 原地构造新值。无论对象之前是否持有值,都能安全地重建,是替换值的最优方式。 -
constexpr void swap(optional& other): 高效地交换两个optional对象的内容。
工具函数与算法支持
-
std::make_optional: 便捷的工厂函数,自动推导类型,可直接从值构造optional。 -
比较运算符: 所有运算符都进行了重载,逻辑直观:两个空对象相等;空对象被认为"小于"任何有值对象。
-
std::swap重载 : 标准swap算法可以直接用于optional对象,高效交换内容。 -
std::hash特化 :optional对象支持哈希计算,可被用作unordered_map等容器的键,哈希值在对象为空时是未指定的。 -
迭代器支持 (C++26起):C++26标准为
std::optional添加了begin()和end()成员函数,使其能像容器一样被迭代,支持范围for循环。
代码示例:
cpp
#include <optional>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cassert>
// ============================================================
// std::optional<T> --- C++17 引入的"可能没有值"的容器
//
// 核心语义: 要么包含一个 T 类型的值,要么为空 (nullopt)。
// 值存储在线 (inline),不涉及堆分配。
// 不能存储引用 (T& 是非法的)。
// ============================================================
// ============================================================
// 1. 构造: 创建 optional 的所有方式
// ============================================================
void demo_construction() {
std::cout << "======== 1. 构造 ========\n";
// --- 1a. 默认构造 / nullopt --- 创建空的 optional ---
std::optional<int> empty1; // 空
std::optional<int> empty2 = std::nullopt; // 空
std::optional<int> empty3(std::nullopt); // 空
std::cout << "默认构造: has_value = " << empty1.has_value() << '\n';
// --- 1b. 从值构造 (隐式) ---
std::optional<int> opt_int = 42;
std::optional<std::string> opt_str = "hello";
std::cout << "从值构造: opt_int = " << *opt_int
<< ", opt_str = " << *opt_str << '\n';
// --- 1c. make_optional --- 工厂函数,免写模板参数 ---
auto opt1 = std::make_optional(42); // optional<int>
auto opt2 = std::make_optional<std::string>("xyz");
auto opt3 = std::make_optional<double>(3.14);
std::cout << "make_optional: " << *opt1 << ", " << *opt2 << ", " << *opt3 << '\n';
// --- 1d. std::in_place --- 就地构造,避免临时对象 ---
// 如果 T 不支持拷贝/移动,这是唯一构造方式
// 方式 A: make_optional + initializer_list
auto opt_vec = std::make_optional<std::vector<int>>({1, 2, 3});
// 方式 B: 直接用 optional 构造函数
std::optional<std::vector<int>> opt_vec2(std::in_place, {4, 5, 6});
std::cout << "in_place vector A: ";
for (int x : *opt_vec) std::cout << x << ' ';
std::cout << "| size = " << opt_vec->size() << '\n';
std::cout << "in_place vector B: ";
for (int x : *opt_vec2) std::cout << x << ' ';
std::cout << "| size = " << opt_vec2->size() << '\n';
// --- 1e. in_place + initializer_list + 额外参数 ---
// 构造 std::vector<int>({1,2,3}, allocator)
auto opt_vec3 = std::make_optional<std::vector<int>>({1, 2, 3}, std::allocator<int>());
std::cout << "in_place+alloc: size = " << opt_vec3->size() << '\n';
// --- 1f. 拷贝/移动构造 ---
auto opt_copy = opt_int; // 拷贝
auto opt_move = std::move(opt_int); // 移动 (opt_int 仍然有值,但 int 是基础类型无影响)
std::cout << "拷贝: " << *opt_copy << ", 移动后原值(仍可访问): " << *opt_int << '\n';
}
// ============================================================
// 2. 观察者: 获取值 / 检查是否为空
// ============================================================
void demo_observers() {
std::cout << "\n======== 2. 观察者 (observers) ========\n";
std::optional<int> present = 100;
std::optional<int> absent;
// --- 2a. has_value() --- 检查是否含有值 ---
std::cout << "present.has_value() = " << present.has_value() << '\n';
std::cout << "absent.has_value() = " << absent.has_value() << '\n';
// --- 2b. operator bool --- 隐式转换为 bool (explicit) ---
// 可以直接在 if 中使用
if (present) {
std::cout << "present 为 true\n";
}
if (!absent) {
std::cout << "absent 为 false\n";
}
// --- 2c. value() --- 安全访问,空时抛出 std::bad_optional_access ---
std::cout << "present.value() = " << present.value() << '\n';
try {
std::cout << absent.value() << '\n'; // 抛出异常
} catch (const std::bad_optional_access& e) {
std::cout << "absent.value() 抛出 bad_optional_access: " << e.what() << '\n';
}
// --- 2d. operator* / operator-> --- 不检查的访问 (未定义行为) ---
std::cout << "*present = " << *present << '\n';
struct Point { int x, y; };
std::optional<Point> opt_pt{Point{3, 4}};
std::cout << "opt_pt->x = " << opt_pt->x << ", opt_pt->y = " << opt_pt->y << '\n';
// 注意: *absent 是 UB,不要这么做!
// --- 2e. value_or() --- 提供默认值,空时返回默认值 ---
int val1 = present.value_or(-1); // 100 (有值)
int val2 = absent.value_or(-1); // -1 (无值)
std::cout << "present.value_or(-1) = " << val1 << '\n';
std::cout << "absent.value_or(-1) = " << val2 << '\n';
// value_or 支持右值引用:
std::optional<std::string> absent_str;
std::string s = absent_str.value_or(std::string("default"));
std::cout << "absent_str.value_or(\"default\") = " << s << '\n';
}
// ============================================================
// 3. 修改器: 修改 optional 的值或状态
// ============================================================
void demo_modifiers() {
std::cout << "\n======== 3. 修改器 (modifiers) ========\n";
// --- 3a. emplace() --- 销毁旧值,在原位构造新值 ---
std::optional<std::string> opt("hello");
std::cout << "原始值: " << *opt << '\n';
opt.emplace("world"); // 销毁 "hello",构造 "world"
std::cout << "emplace(\"world\") 后: " << *opt << '\n';
// emplace 也可以给空 optional 构造值:
std::optional<std::string> empty_opt;
empty_opt.emplace("constructed!");
std::cout << "emplace 到空 optional: " << *empty_opt << '\n';
// emplace 返回新构造值的引用:
std::string& ref = opt.emplace("new");
ref[0] = 'N'; // 修改 "new" → "New"
std::cout << "emplace 返回引用: " << *opt << '\n';
// 就地构造带多个参数的类型:
std::optional<std::vector<int>> opt_vec;
opt_vec.emplace(5, 99); // vector<int>(5, 99) → {99,99,99,99,99}
std::cout << "emplace(5, 99): size=" << opt_vec->size()
<< ", [0]=" << (*opt_vec)[0] << '\n';
// --- 3b. reset() --- 销毁值,变为空 ---
opt.reset();
std::cout << "reset() 后: has_value = " << opt.has_value() << '\n';
// 对空的 optional 调用 reset() 是安全且无操作的
// --- 3c. swap() --- 交换两个 optional 的内容 ---
std::optional<int> a = 10;
std::optional<int> b = 20;
a.swap(b);
std::cout << "a.swap(b) 后: a = " << *a << ", b = " << *b << '\n';
// 与空值交换:
std::optional<int> c;
b.swap(c);
std::cout << "b.swap(空) 后: b.has_value = " << b.has_value()
<< ", c = " << *c << '\n';
// --- 3d. operator= --- 各种赋值 ---
std::optional<int> opt_int;
// 从值赋值:
opt_int = 42;
std::cout << "= 42: " << *opt_int << '\n';
// 从 nullopt 赋值 (变为空):
opt_int = std::nullopt;
std::cout << "= nullopt 后: has_value = " << opt_int.has_value() << '\n';
// 从另一个 optional 赋值:
std::optional<int> src = 99;
opt_int = src;
std::cout << "= optional<int> 后: " << *opt_int << '\n';
}
// ============================================================
// 4. 比较运算: ==, !=, <, <=, >, >=
// ============================================================
void demo_comparison() {
std::cout << "\n======== 4. 比较运算 ========\n";
std::optional<int> a = 10;
std::optional<int> b = 20;
std::optional<int> empty;
// --- 与 optional 比较 ---
std::cout << "a == b : " << (a == b) << '\n';
std::cout << "a != b : " << (a != b) << '\n';
std::cout << "a < b : " << (a < b) << '\n';
// 空 optional 参与的规则:
// nullopt 被认为比任何有值 optional 都"小"
// 两个空 optional 相等
std::cout << "empty < a : " << (empty < a) << '\n'; // true
std::cout << "empty == nullopt : " << (empty == std::nullopt) << '\n'; // true
std::cout << "a == nullopt : " << (a == std::nullopt) << '\n'; // false
std::cout << "a != nullopt : " << (a != std::nullopt) << '\n'; // true
// --- 与底层值类型直接比较 ---
std::cout << "a == 10 : " << (a == 10) << '\n'; // true
std::cout << "a < 100 : " << (a < 100) << '\n'; // true
// 注意: 空 optional 与值比较总是 false (空 != 任何值)
std::cout << "empty == 0 : " << (empty == 0) << '\n'; // false
}
// ============================================================
// 5. 实际应用场景
// ============================================================
// 场景 1: 查找可能不存在的键
std::optional<std::string> find_value(const std::map<int, std::string>& m, int key) {
auto it = m.find(key);
if (it != m.end()) {
return it->second; // 找到了
} else {
return std::nullopt; // 没找到
}
}
// 场景 2: 可能失败的解析 --- 不需要异常或 out-parameter
std::optional<int> parse_int(const std::string& s) {
try {
return std::stoi(s);
} catch (const std::invalid_argument&) {
return std::nullopt;
} catch (const std::out_of_range&) {
return std::nullopt;
}
}
// 场景 3: 延迟初始化 --- 成员变量可能稍后才赋值
class Config {
std::optional<std::string> cached_path_;
public:
void set_path(const std::string& p) { cached_path_ = p; }
std::string get_path() const {
return cached_path_.value_or("/etc/default");
}
};
void demo_real_world() {
std::cout << "\n======== 5. 实际应用场景 ========\n";
// --- 场景 1: 查找 ---
std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
auto found = find_value(m, 2);
if (found) {
std::cout << "找到 key=2: " << *found << '\n';
}
auto missing = find_value(m, 99);
std::cout << "key=99: " << missing.value_or("(未找到)") << '\n';
// --- 场景 2: 解析 ---
auto n1 = parse_int("12345");
auto n2 = parse_int("not_a_number");
std::cout << "parse \"12345\" = " << n1.value_or(-1) << '\n';
std::cout << "parse \"not_a_number\" = " << n2.value_or(-1) << " (fallback)\n";
// --- 场景 3: 延迟初始化 ---
Config cfg;
std::cout << "默认路径: " << cfg.get_path() << '\n';
cfg.set_path("/custom/path");
std::cout << "设置后路径: " << cfg.get_path() << '\n';
}
// ============================================================
// 6. optional 与 std::nullopt / std::bad_optional_access
// ============================================================
void demo_helpers() {
std::cout << "\n======== 6. 辅助类型 ========\n";
// --- std::nullopt --- 空 optional 的"空值"常量 ---
// 类型为 std::nullopt_t,常用于:
// 1. 创建空 optional
// 2. 将 optional 置空
// 3. 函数返回空值
std::optional<int> opt = 42;
opt = std::nullopt; // 置空
std::cout << "置 nullopt 后: has_value = " << opt.has_value() << '\n';
// 函数返回空值:
auto create = [](bool ok) -> std::optional<std::string> {
if (ok) return "success";
else return std::nullopt; // 空
};
std::cout << "create(true) = " << *create(true) << '\n';
std::cout << "create(false).has_value = " << create(false).has_value() << '\n';
// --- std::bad_optional_access --- 访问空 optional 抛出的异常 ---
// 继承自 std::logic_error,提供 what() 信息
std::optional<int> empty;
try {
empty.value(); // 抛出 std::bad_optional_access
} catch (const std::bad_optional_access& e) {
std::cout << "捕获 bad_optional_access: " << e.what() << '\n';
}
}
int main() {
demo_construction();
demo_observers();
demo_modifiers();
demo_comparison();
demo_real_world();
demo_helpers();
std::cout << "\n所有演示完毕。\n";
return 0;
}
variant
官方文档:Standard library header <variant> (C++17) - cppreference.com
<variant> 在 C++17 标准库中是实现类型安全联合体(Type-safe Union)的核心头文件。它比传统的 union 更强大和安全,不仅知道当前存储的值是哪个类型,还能管理非平凡(non-trivial)类型的生命周期(构造/析构)。
| 组件 | 功能描述 |
|---|---|
std::variant<Types...> |
核心类模板,它是一个类型安全的联合体,可以持有其类型列表中任意一种类型的值。 |
std::monostate |
一个空类型,用于使 variant 可以默认构造,或表示一个"空"状态。 |
std::bad_variant_access |
异常类,当错误地访问 variant 对象的值时(例如,用 get 获取一个不匹配的类型)会抛出此异常。 |
| 辅助类型特征 | std::variant_size 和 std::variant_alternative 用于在编译时查询 variant 的可选类型数量和特定索引对应的类型。 |
std::variant_npos |
一个常量,表示 variant 处于非法状态(即 valueless_by_exception 返回 true 时的索引)。 |
接口
std::variant 的大部分操作都围绕着"构造/赋值 -> 检查 -> 访问 -> 修改"这个生命周期展开。
构造与赋值
提供了非常灵活的构造方式,可以直接赋值,也可以原位构造。
-
默认/值构造 :可以直接用
variant<int, std::string> v;默认构造(若第一个类型支持),或用v = 10;直接赋值。 -
原位构造 (
emplace) :使用v.emplace<std::string>("hello")或v.emplace<1>("hello"),直接在内部构建新值,避免临时对象拷贝,效率更高。 -
std::in_place_type/std::in_place_index:用于在构造时显式指定要激活的类型或索引,防止歧义,例如std::variant<std::string, int> v(std::in_place_type<std::string>, "hello");。
状态观测:询问"现在是谁?"
在访问值之前,必须先知道它当前存储的是哪个类型的值。
-
constexpr size_t index() const noexcept:返回当前存储的值的类型在Types...列表中的零基索引。 -
constexpr bool valueless_by_exception() const noexcept:检查variant是否处于因异常导致的"无值"非法状态(极少发生)。
值访问:安全地提取数据
这些方法提供了不同安全级别的访问,完美体现了"先检查,后使用"的设计哲学。
-
std::get<T>(v)/std::get<Index>(v):不安全访问,但代码简洁。若类型或索引不匹配,会抛出std::bad_variant_access异常。 -
std::get_if<T>(&v)/std::get_if<Index>(&v):安全访问,返回一个指针 。若类型/索引匹配,返回指向值的指针;否则返回nullptr。这是编写健壮代码的首选。 -
std::visit:最强大的工具,允许你传入一个可调用对象(如泛型 lambda),其内部会自动根据当前激活的类型调用对应的重载或模板实例。
比较与修改
-
constexpr void swap(variant& other):交换两个variant对象的内容。 -
比较运算符 :
variant对象可以像普通值一样进行所有比较操作(==,!=,<,<=,>,>=)。比较规则是:先比较哪个类型的索引小,索引相同再比值。 -
std::hash特化 :variant支持哈希,因此可以作为std::unordered_map等容器的键。
代码示例
cpp
#include <variant>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <iomanip>
// ============================================================
// std::variant<Types...> --- C++17 引入的类型安全的联合体
//
// 核心语义: 任意时刻持有 Types 中 **某一种** 类型的值。
// 替代 C 的 union (union 只能存 POD, variant 能存任何类型)。
// 所有值存于栈上, 不涉及动态分配。
// ============================================================
// ============================================================
// 1. 构造: 创建 variant 的所有方式
// ============================================================
void demo_construction() {
std::cout << "======== 1. 构造 (Construction) ========\n";
// --- 1a. 默认构造:持有第一种类型的值初始化 (值初始化) ---
// 要求第一种类型 (int) 必须可默认构造
std::variant<int, double, std::string> v1;
std::cout << "默认构造: v1.index() = " << v1.index()
<< " (索引0=int), value = " << std::get<int>(v1) << '\n';
// v1 持有 int{} → 0
// --- 1b. 从值构造 (直接推导类型) ---
std::variant<int, double, std::string> v2 = 42;
std::variant<int, double, std::string> v3 = 3.14;
std::variant<int, double, std::string> v4 = std::string("hello");
std::cout << "从值构造: v2=" << std::get<int>(v2)
<< ", v3=" << std::get<double>(v3)
<< ", v4=" << std::get<std::string>(v4) << '\n';
// --- 1c. std::in_place_type<T> --- 明确构造哪个类型 ---
// 当多个类型都能从同一实参构造时, 需要消除歧义
// 例如 variant<string, vector<char>> --- 两者都能从 const char* 构造
std::variant<std::string, std::vector<char>> v_str(
std::in_place_type<std::string>, "hello");
std::cout << "in_place_type<string> = " << std::get<std::string>(v_str) << '\n';
// --- 1d. std::in_place_index<I> --- 按索引构造 ---
std::variant<int, double, std::string> v_idx(
std::in_place_index<2>, "world"); // 索引2 = std::string
std::cout << "in_place_index<2> = " << std::get<2>(v_idx) << '\n';
// --- 1e. std::monostate --- 让 variant 成为 optional-like ---
// variant<int, float> 无法"为空"。用 monostate 作为第一个类型
// 可以表示"无值"状态, 类似 optional
std::variant<std::monostate, int, std::string> maybe_int;
std::cout << "monostate variant: index = " << maybe_int.index()
<< " (monostate), 可以用它表示空状态\n";
}
// ============================================================
// 2. 获取值: index / get / get_if / holds_alternative
// ============================================================
void demo_access() {
std::cout << "\n======== 2. 获取值 (Access) ========\n";
std::variant<int, double, std::string> v(3.14);
// --- 2a. index() --- 获取当前值的类型索引 (0-based) ---
std::cout << "v.index() = " << v.index() << " (1 = double)\n";
// --- 2b. std::get<T>() --- 按类型取值 (失败时抛异常) ---
std::cout << "std::get<double>(v) = " << std::get<double>(v) << '\n';
try {
std::get<int>(v); // v 当前存 double, 不是 int
} catch (const std::bad_variant_access& e) {
std::cout << "std::get<int>(v) 抛出 bad_variant_access: "
<< e.what() << '\n';
}
// --- 2c. std::get<I>() --- 按索引取值 (编译时类型安全) ---
std::cout << "std::get<1>(v) = " << std::get<1>(v) << '\n';
// std::get<0>(v); // 编译失败: 索引0是int, 而v当前存double
// 但 get<I> 仍然会抛 bad_variant_access 如果运行时索引不匹配
// --- 2d. std::get_if<T>() --- 按类型取指针 (失败返回 nullptr) ---
// 不抛异常, 用指针语义
if (auto* pval = std::get_if<double>(&v)) {
std::cout << "get_if<double> 成功: " << *pval << '\n';
}
if (auto* pval = std::get_if<int>(&v)) {
std::cout << "get_if<int> 成功: " << *pval << '\n';
} else {
std::cout << "get_if<int> 返回 nullptr (v 当前不是 int)\n";
}
// --- 2e. std::get_if<I>() --- 按索引取指针 ---
if (auto* pval = std::get_if<1>(&v)) {
std::cout << "get_if<1> 成功: " << *pval << '\n';
}
// --- 2f. std::holds_alternative<T>() --- 仅判断类型, 不取值 ---
std::cout << "holds_alternative<int>(v) = "
<< std::holds_alternative<int>(v) << '\n';
std::cout << "holds_alternative<double>(v) = "
<< std::holds_alternative<double>(v) << '\n';
}
// ============================================================
// 3. 修改: emplace / operator= / swap / valueless_by_exception
// ============================================================
void demo_modifiers() {
std::cout << "\n======== 3. 修改 (Modifiers) ========\n";
std::variant<int, double, std::string> v(10);
std::cout << "初始值: " << std::get<int>(v) << '\n';
// --- 3a. emplace<T>() --- 就地构造新类型, 销毁旧值 ---
v.emplace<std::string>("replaced!");
std::cout << "emplace<std::string> 后: "
<< std::get<std::string>(v) << '\n';
// --- 3b. emplace<I>() --- 按索引就地构造 ---
v.emplace<0>(99); // 索引0 = int
std::cout << "emplace<0> 后: " << std::get<0>(v) << '\n';
// --- 3c. operator= --- 从值赋值 (自动切换类型) ---
v = 3.14159; // → 变成 double
std::cout << "v = 3.14159: " << std::get<double>(v) << '\n';
v = std::string("assigned"); // → 变成 string
std::cout << "v = \"assigned\": " << std::get<std::string>(v) << '\n';
// --- 3d. swap() --- 交换两个 variant ---
std::variant<int, double, std::string> a(100);
std::variant<int, double, std::string> b(200);
std::cout << "交换前: a=" << std::get<int>(a)
<< ", b=" << std::get<int>(b) << '\n';
a.swap(b);
std::cout << "交换后: a=" << std::get<int>(a)
<< ", b=" << std::get<int>(b) << '\n';
// --- 3e. valueless_by_exception() --- 异常安全标记 ---
// 正常情况下永远为 false。只有当 emplace/赋值
// 过程中抛出异常且无法恢复原值时才会变为 true。
// 处于 valueless 状态时, index() 返回 variant_npos。
std::cout << "valueless_by_exception = "
<< v.valueless_by_exception() << '\n';
}
// ============================================================
// 4. std::visit --- variant 的"瑞士军刀"
// ============================================================
// 访问者: 重载的 operator() --- 每个类型一个重载
struct Stringifier {
std::string operator()(int i) const {
return "int: " + std::to_string(i);
}
std::string operator()(double d) const {
std::ostringstream oss;
oss << std::fixed << std::setprecision(2) << d;
return "double: " + oss.str();
}
std::string operator()(const std::string& s) const {
return "string: \"" + s + "\"";
}
};
// C++17 惯用法: 用泛型 lambda 或模板化的 operator()
// overloaded 模式 --- 组合多个 lambda (P0218不需要额外库!)
template <typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// C++20 起有类模板实参推导 (CTAD) 可省略下面这行
template <typename... Ts> overloaded(Ts...) -> overloaded<Ts...>;
void demo_visit() {
std::cout << "\n======== 4. std::visit (访问者) ========\n";
std::vector<std::variant<int, double, std::string>> items = {
42, 3.14159, std::string("world"), 7, 2.71828
};
// --- 4a. visit + 有名字的访问者 (struct) ---
std::cout << "--- 使用 struct 访问者 ---\n";
for (const auto& v : items) {
std::cout << " " << std::visit(Stringifier{}, v) << '\n';
}
// --- 4b. visit + overloaded lambda (最常用的惯用法) ---
std::cout << "\n--- 使用 overloaded lambda ---\n";
auto visitor = overloaded{
[](int i) { std::cout << " int: " << i << '\n'; },
[](double d) { std::cout << " double: " << d << '\n'; },
[](const std::string& s) { std::cout << " string: " << s << '\n'; }
};
for (const auto& v : items) {
std::visit(visitor, v);
}
// --- 4c. visit + 返回值 ---
auto get_type_name = overloaded{
[](int) -> std::string { return "int"; },
[](double) -> std::string { return "double"; },
[](const std::string&) -> std::string { return "string"; }
};
for (const auto& v : items) {
std::cout << " 当前类型: " << std::visit(get_type_name, v) << '\n';
}
// --- 4d. visit 可以同时访问多个 variant ---
std::variant<int, double> a(10);
std::variant<int, double> b(3.5);
auto sum_visitor = overloaded{
[](int x, int y) -> double { return x + y; },
[](double x, double y) -> double { return x + y; },
[](int x, double y) -> double { return x + y; },
[](double x, int y) -> double { return x + y; }
};
std::cout << "\n多variant visit: visit(visitor, a=10, b=3.5) = "
<< std::visit(sum_visitor, a, b) << '\n';
}
// ============================================================
// 5. 比较运算
// ============================================================
void demo_comparison() {
std::cout << "\n======== 5. 比较运算 (Comparison) ========\n";
std::variant<int, double, std::string> v1(10);
std::variant<int, double, std::string> v2(20);
std::variant<int, double, std::string> v3(10);
std::variant<int, double, std::string> v4(3.14); // 不同类型!
// 同一个类型, 同一个值 → 相等
std::cout << "v1(10) == v3(10): " << (v1 == v3) << '\n';
std::cout << "v1(10) != v2(20): " << (v1 != v2) << '\n';
std::cout << "v1(10) < v2(20): " << (v1 < v2) << '\n';
// 不同类型的比较: 按 index() 比较!
// index 0 (int) < index 1 (double), 因此 v1 < v4 为 true
std::cout << "v1(index=0, int) < v4(index=1, double): "
<< (v1 < v4) << " (按索引比较, 而非按值!)\n";
// 规则: 先比较 index(), index 不同→返回 index 的比较结果
// index 相同→比较底层类型的值
// 注意: variant 不能直接与底层值比较 (GCC12), 需用 get 或构造同类型 variant
// 方式A: 取出值比较
if (std::holds_alternative<int>(v1)) {
std::cout << "v1 (int) == 10: " << (std::get<int>(v1) == 10) << '\n';
}
// 方式B: 构造同类型 variant 比较
std::cout << "v1 == variant(10): " << (v1 == std::variant<int, double, std::string>(10)) << '\n';
}
// ============================================================
// 6. 辅助工具: monostate / bad_variant_access / variant_npos
// ============================================================
void demo_helpers() {
std::cout << "\n======== 6. 辅助类型 ========\n";
// --- std::monostate: variant 的"空"状态 ---
// 用途1: 让 variant 可以表示"没有值" (类似 optional)
std::variant<std::monostate, int, std::string> maybe;
std::cout << "默认: index=" << maybe.index() << " (monostate)\n";
maybe = 42;
std::cout << "赋值后: index=" << maybe.index() << " (int)\n";
// 用途2: 在 std::expected-like 用法中表示成功(无额外数据)
// variant<monostate, error_code> --- monostate=成功, error=出错
// --- std::bad_variant_access ---
std::variant<int, double> v(3.14);
try {
std::get<int>(v); // 当前是 double
} catch (const std::bad_variant_access& e) {
std::cout << "bad_variant_access: " << e.what() << '\n';
}
// --- std::variant_npos ---
// 当 valueless_by_exception() == true 时,
// index() 返回 std::variant_npos 而不是合法索引
std::cout << "variant_npos = " << std::variant_npos << '\n';
std::cout << "当前 v.index() = " << v.index()
<< " (正常值, != variant_npos)\n";
// --- std::variant_size_v / std::variant_alternative_t ---
using V = std::variant<int, double, std::string>;
std::cout << "variant_size_v<V> = " << std::variant_size_v<V> << '\n';
// variant_alternative_t<1, V> 即 double
using SecondType = std::variant_alternative_t<1, V>;
std::cout << "alternative<1,V>::type double? "
<< std::is_same_v<SecondType, double> << '\n';
// --- std::hash ---
// variant<Types...> 可哈希 (从 C++20 开始可选, 取决于所含类型)
std::variant<int, std::string> v1_hash(42);
std::variant<int, std::string> v2_hash(42);
std::cout << "hash(v1)=hash(v2)? "
<< (std::hash<decltype(v1_hash)>{}(v1_hash)
== std::hash<decltype(v2_hash)>{}(v2_hash)) << '\n';
}
// ============================================================
// 7. 实际应用: 状态机 / 消息传递
// ============================================================
struct ConnectReq { std::string host; int port; };
struct DisconnectReq {};
struct SendDataReq { std::vector<unsigned char> data; };
struct TimeoutEv { int ms; };
using NetworkMsg = std::variant<ConnectReq, DisconnectReq, SendDataReq, TimeoutEv>;
void handle_message(const NetworkMsg& msg) {
std::visit(overloaded{
[](const ConnectReq& c) {
std::cout << " 连接请求: " << c.host << ":" << c.port << '\n';
},
[](const DisconnectReq&) {
std::cout << " 断开连接请求\n";
},
[](const SendDataReq& s) {
std::cout << " 发送数据: " << s.data.size() << " bytes\n";
},
[](const TimeoutEv& t) {
std::cout << " 超时事件: " << t.ms << "ms\n";
}
}, msg);
}
void demo_state_machine() {
std::cout << "\n======== 7. 实际应用: 消息状态机 ========\n";
std::vector<NetworkMsg> messages = {
ConnectReq{"localhost", 8080},
SendDataReq{{0x48, 0x65, 0x6c, 0x6c, 0x6f}},
TimeoutEv{5000},
DisconnectReq{}
};
for (const auto& msg : messages) {
handle_message(msg);
}
}
int main() {
demo_construction();
demo_access();
demo_modifiers();
demo_visit();
demo_comparison();
demo_helpers();
demo_state_machine();
std::cout << "\n所有演示完毕。\n";
return 0;
}
any
官方文档:Standard library header <any> (C++17) - cppreference.com
<any> 是 C++17 标准库引入的一个关键头文件,它定义了 std::any 类,为C++提供了真正的类型安全擦除容器
<any> 头文件提供了三个核心组件,其中最主要的是 std::any 类。
-
std::any类:核心容器,可持有任意可复制构造类型(CopyConstructible)的值,也可以是空状态。 -
std::bad_any_cast异常类 :当std::any_cast进行类型转换失败时抛出的异常。 -
std::make_any/std::any_cast工具函数 :用于创建和访问any对象的非成员函数,提供了极大的便利性和安全性。
接口
std::any 的操作接口紧密围绕其"存储任意值"这一核心能力展开。
构造与赋值 (Construction & Assignment)
提供了非常灵活的构造方式,可以直接赋值,也可以原位构造,以追求最佳效率。
-
any()/any(std::nullopt_t):默认构造,创建空any对象。 -
any(const T& value)/any(T&& value):通过拷贝或移动直接包含一个值,例如std::any a = 42;。 -
any(std::in_place_type<T>, args...):使用std::in_place_type进行原位构造,直接在any内部构建对象,能有效避免不必要的拷贝。 -
operator=(const T& rhs)/operator=(T&& rhs):通过赋值操作符包含新值,会正确析构旧值。 -
emplace<T>(args...):原位构造新值,直接替换any对象持有的值,无论其之前是否为空。
状态观测 (Observers)
在访问值之前,检查其存在性和类型是非常关键的安全实践。
-
has_value():返回bool值,指示对象当前是否持有值。 -
type():返回当前持有值的std::type_info引用。如果对象为空,返回typeid(void)。此方法常用于类型分发,如if (a.type() == typeid(int))。
值访问 (Value Access)
这些方法提供了不同安全级别的访问,是 std::any 类型安全的核心体现。
-
std::any_cast<T>(any_obj):安全访问的值返回形式 。若类型匹配则返回值的拷贝,否则抛出std::bad_any_cast异常。 -
std::any_cast<T&>(any_obj):安全访问的引用返回形式。可以避免拷贝,直接获取内部值的引用。类型不匹配时同样抛出异常。 -
std::any_cast<T*>(any_obj_ptr):安全访问的指针形式 。这是最安全 的方式。接收一个std::any的指针,若类型匹配则返回指向内部值的指针,否则返回nullptr,不抛出异常。
修改器与工具函数 (Modifiers & Utility Functions)
-
reset():销毁持有的对象,将any变为空状态。 -
swap():交换两个any对象的内容。 -
std::make_any<T>(args...):一个便捷的工厂函数,相当于std::any(std::in_place_type<T>, args...),用于原位构造any对象。
代码示例
cpp
#include <any>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cassert>
#include <typeinfo>
#include <iomanip>
// ============================================================
// std::any --- C++17 引入的类型安全的"万能容器"
//
// 核心语义: 可以持有任意可拷贝构造类型的值, 类型信息在运行时保留。
//
// 与 void* 的区别: any 类型安全 (拒绝非法转换)
// 与 variant 的区别: any 的类型集是开放的 (不需要提前列举)
// 与 optional 的区别: any 没有"已知类型"的限制 (任何类型都能存)
//
// 代价: 使用类型擦除, 可能涉及堆分配 (小对象优化 SBO 除外)
// ============================================================
// ============================================================
// 1. 构造: 创建 any 的所有方式
// ============================================================
void demo_construction() {
std::cout << "======== 1. 构造 (Construction) ========\n";
// --- 1a. 默认构造 --- 空 any (没有值) ---
std::any empty1;
std::any empty2{};
std::cout << "默认构造: has_value = " << empty1.has_value() << '\n';
// --- 1b. 从任意值构造 --- 隐式转换 ---
std::any a_int = 42;
std::any a_dbl = 3.14;
std::any a_str = std::string("hello");
std::any a_cstr = "world"; // const char[6] → const char*
std::any a_vec = std::vector<int>{1, 2, 3};
std::cout << "从值构造: int=" << std::any_cast<int>(a_int)
<< ", double=" << std::any_cast<double>(a_dbl)
<< ", string=" << std::any_cast<std::string>(a_str)
<< ", cstr=" << std::any_cast<const char*>(a_cstr)
<< ", vec[0]=" << std::any_cast<std::vector<int>>(a_vec)[0] << '\n';
// --- 1c. std::in_place_type<T> --- 就地构造 (避免临时对象) ---
std::any a_inplace(std::in_place_type<std::string>, "constructed in-place");
std::cout << "in_place_type: " << std::any_cast<std::string>(a_inplace) << '\n';
// --- 1d. in_place_type + initializer_list ---
std::any a_vec2(std::in_place_type<std::vector<int>>, {10, 20, 30});
auto& vec_ref = std::any_cast<std::vector<int>&>(a_vec2);
std::cout << "in_place+init_list: size=" << vec_ref.size()
<< ", [1]=" << vec_ref[1] << '\n';
// --- 1e. std::make_any --- 工厂函数 (免写模板参数) ---
auto a1 = std::make_any<int>(100);
auto a2 = std::make_any<std::string>("factory");
auto a3 = std::make_any<std::vector<int>>(5, 7); // 5 elements of 7
std::cout << "make_any<int>: " << std::any_cast<int>(a1)
<< ", make_any<string>: " << std::any_cast<std::string>(a2)
<< ", make_any<vector>: size=" << std::any_cast<std::vector<int>&>(a3).size()
<< '\n';
// --- 1f. make_any + initializer_list ---
auto a4 = std::make_any<std::vector<int>>({1, 2, 3, 4, 5});
std::cout << "make_any<vector>(init_list): size="
<< std::any_cast<std::vector<int>&>(a4).size() << '\n';
// --- 1g. 拷贝/移动构造 ---
std::any copy = a_int; // 拷贝
std::any moved = std::move(a_int); // 移动 (a_int 变为空)
std::cout << "拷贝后: " << std::any_cast<int>(copy)
<< ", 移动后原值 has_value = " << a_int.has_value() << '\n';
}
// ============================================================
// 2. 观察者: has_value / type
// ============================================================
void demo_observers() {
std::cout << "\n======== 2. 观察者 (Observers) ========\n";
std::any a_int = 42;
std::any a_str = std::string("hello");
std::any empty;
// --- 2a. has_value() --- 是否持有值 ---
std::cout << "a_int.has_value() = " << a_int.has_value() << '\n';
std::cout << "a_str.has_value() = " << a_str.has_value() << '\n';
std::cout << "empty.has_value() = " << empty.has_value() << '\n';
// --- 2b. type() --- 运行时获取类型信息 (返回 const std::type_info&) ---
std::cout << "a_int.type().name() = " << a_int.type().name() << '\n';
std::cout << "a_str.type().name() = " << a_str.type().name() << '\n';
// 比较类型:
std::cout << "a_int.type() == typeid(int) : "
<< (a_int.type() == typeid(int)) << '\n';
std::cout << "a_str.type() == typeid(std::string) : "
<< (a_str.type() == typeid(std::string)) << '\n';
std::cout << "a_int.type() == typeid(double) : "
<< (a_int.type() == typeid(double)) << '\n';
// 空 any 的 type(): 返回 typeid(void)
std::cout << "empty.type() == typeid(void): "
<< (empty.type() == typeid(void)) << '\n';
}
// ============================================================
// 3. 取值: any_cast 的 5 种重载
// ============================================================
void demo_any_cast() {
std::cout << "\n======== 3. any_cast --- 取值 ========\n";
// --- 3a. any_cast<T>(any&) --- 返回 T 的拷贝 (类型不匹配时抛异常) ---
std::any a = 42;
int val = std::any_cast<int>(a); // 拷贝
std::cout << "any_cast<int>(a) = " << val << '\n';
try {
std::any_cast<double>(a); // a 存的是 int, 不是 double
} catch (const std::bad_any_cast& e) {
std::cout << "any_cast<double>(int_any) 抛出 bad_any_cast: "
<< e.what() << '\n';
}
// --- 3b. any_cast<T&>(any&) --- 返回引用, 可以修改原值 ---
std::any a_str = std::string("hello");
std::any_cast<std::string&>(a_str) += " world!"; // 修改内部值
std::cout << "any_cast<string&> 修改后: "
<< std::any_cast<std::string>(a_str) << '\n';
// --- 3c. any_cast<T&&>(any&&) --- 移动取出值 ---
std::any a_tmp = std::string("will be moved");
std::string stolen = std::any_cast<std::string>(std::move(a_tmp));
std::cout << "移动取出: " << stolen << '\n';
// a_tmp 不再持有完整的 string (被移走)
// --- 3d. any_cast<T>(const any*) --- 安全访问 (const), 不匹配返回 nullptr ---
const std::any* pa = &a;
if (const int* pint = std::any_cast<int>(pa)) {
std::cout << "any_cast<int>(const any*) 成功: " << *pint << '\n';
}
if (const double* pdbl = std::any_cast<double>(pa)) {
std::cout << "this never prints\n";
} else {
std::cout << "any_cast<double>(const any*) 返回 nullptr (类型不匹配)\n";
}
// --- 3e. any_cast<T>(any*) --- 安全访问 (非 const), 可修改 ---
if (int* pint = std::any_cast<int>(&a)) {
*pint += 10; // 修改 a 内部的值
}
std::cout << "any_cast<int>(any*) 修改后: "
<< std::any_cast<int>(a) << '\n';
}
// ============================================================
// 4. 修改器: emplace / reset / swap / operator=
// ============================================================
void demo_modifiers() {
std::cout << "\n======== 4. 修改器 (Modifiers) ========\n";
std::any a(10);
std::cout << "初始值: " << std::any_cast<int>(a) << '\n';
// --- 4a. operator= --- 从值赋值 (切换类型) ---
a = 3.14159; // int → double
std::cout << "a = 3.14: " << std::any_cast<double>(a) << '\n';
a = std::string("changed"); // double → string
std::cout << "a = \"changed\": " << std::any_cast<std::string>(a) << '\n';
// --- 4b. emplace<T>() --- 就地构造, 避免临时对象 ---
a.emplace<std::string>("emplaced value");
std::cout << "emplace<string>: " << std::any_cast<std::string>(a) << '\n';
// emplace 返回新值的引用:
std::any vec_any = std::vector<int>{1, 2, 3};
auto& vref = vec_any.emplace<std::vector<int>>(4, 99); // 4 elements of 99
vref.push_back(100);
std::cout << "emplace<vector> + 修改: size=" << vref.size()
<< ", [0]=" << vref[0] << ", last=" << vref.back() << '\n';
// --- 4c. reset() --- 清空为无值状态 ---
std::any temp = 42;
std::cout << "reset 前: has_value = " << temp.has_value() << '\n';
temp.reset();
std::cout << "reset 后: has_value = " << temp.has_value()
<< ", type = " << temp.type().name() << '\n';
// --- 4d. swap() --- 交换两个 any 的内容 ---
std::any x = 100;
std::any y = std::string("two hundred");
std::cout << "交换前: x=int(" << std::any_cast<int>(x)
<< "), y=string(" << std::any_cast<std::string>(y) << ")\n";
x.swap(y);
std::cout << "交换后: x=string(" << std::any_cast<std::string>(x)
<< "), y=int(" << std::any_cast<int>(y) << ")\n";
// 与空值交换:
std::any z;
y.swap(z);
std::cout << "与空交换后: y.has_value = " << y.has_value()
<< ", z = " << std::any_cast<int>(z) << '\n';
}
// ============================================================
// 5. 实际应用场景
// ============================================================
// 场景 1: 类型擦除的配置存储
using ConfigMap = std::map<std::string, std::any>;
ConfigMap make_config() {
ConfigMap cfg;
cfg["timeout"] = 30; // int
cfg["host"] = std::string("localhost"); // string
cfg["port"] = 8080; // int
cfg["verbose"] = true; // bool
cfg["retry_delay"] = 1.5; // double
return cfg;
}
void demo_config_store() {
std::cout << "\n======== 5. 实际应用: 配置存储 ========\n";
auto cfg = make_config();
// 读取时按类型取出:
auto timeout = std::any_cast<int>(cfg["timeout"]);
auto host = std::any_cast<std::string>(cfg["host"]);
auto verbose = std::any_cast<bool>(cfg["verbose"]);
std::cout << "timeout = " << timeout << '\n';
std::cout << "host = " << host << '\n';
std::cout << "verbose = " << std::boolalpha << verbose << '\n';
}
// 场景 2: 通用的"任意消息"队列 (调度事件)
struct EventBus {
std::vector<std::any> events;
template <typename T>
void post(T&& event) {
events.emplace_back(std::forward<T>(event));
}
template <typename T>
void process_one() {
for (auto& e : events) {
if (e.type() == typeid(T)) {
auto& typed_event = std::any_cast<T&>(e);
std::cout << " 处理事件: " << typed_event << '\n';
}
}
}
};
void demo_event_bus() {
std::cout << "\n======== 6. 实际应用: 事件总线 ========\n";
EventBus bus;
bus.post(std::string("UserLoggedIn"));
bus.post(404);
bus.post(std::string("PageRequested"));
bus.post(200);
std::cout << "共 " << bus.events.size() << " 个事件\n";
std::cout << "\n处理 string 事件:\n";
bus.process_one<std::string>();
std::cout << "\n处理 int 事件:\n";
bus.process_one<int>();
}
// 场景 3: 通用回调参数 (替代 void*)
void invoke_with_args(const std::vector<std::any>& args) {
std::cout << "\n======== 7. 通用参数传递 ========\n";
for (size_t i = 0; i < args.size(); ++i) {
const auto& arg = args[i];
// 运行时类型分发
if (arg.type() == typeid(int))
std::cout << " [" << i << "] int : " << std::any_cast<int>(arg) << '\n';
else if (arg.type() == typeid(double))
std::cout << " [" << i << "] double : " << std::any_cast<double>(arg) << '\n';
else if (arg.type() == typeid(std::string))
std::cout << " [" << i << "] string : " << std::any_cast<std::string>(arg) << '\n';
else if (arg.type() == typeid(const char*))
std::cout << " [" << i << "] cstr : " << std::any_cast<const char*>(arg) << '\n';
else
std::cout << " [" << i << "] (unknown type: " << arg.type().name() << ")\n";
}
}
// ============================================================
// 8. any vs variant vs optional 对比
// ============================================================
void demo_comparison() {
std::cout << "\n======== 8. any / variant / optional 对比 ========\n";
std::cout << R"(
┌──────────────┬───────────────────┬───────────────────┬───────────────────┐
│ 特性 │ std::optional<T> │ std::variant<Ts..>│ std::any │
├──────────────┼───────────────────┼───────────────────┼───────────────────┤
│ 类型集 │ 1 种 (T 或空) │ 多种(需提前列举) │ 任意 (开放集合) │
│ 编译时类型可达 │ 是 │ 是 │ 否 (运行时查询) │
│ 内存分配 │ 栈上 (inline) │ 栈上 (inline) │ 可能堆分配 │
│ 空状态 │ 有 (nullopt) │ 有 (monostate) │ 有 (空 any) │
│ 关键访问 │ value() / * │ get<T>() / visit │ any_cast<T>() │
│ 适用场景 │ "可能没有值" │ "N选1" │ "任意一种" │
└──────────────┴───────────────────┴───────────────────┴───────────────────┘
)";
}
int main() {
demo_construction();
demo_observers();
demo_any_cast();
demo_modifiers();
demo_config_store();
demo_event_bus();
std::vector<std::any> args;
args.push_back(42);
args.push_back(3.14159);
args.push_back(std::string("hello"));
args.push_back("c-string");
invoke_with_args(args);
demo_comparison();
std::cout << "\n所有演示完毕。\n";
return 0;
}
string_view
官方文档:Standard library header <string_view> (C++17) - cppreference.com
<string_view> 是 C++17 标准库的重要组成部分,它通过 std::string_view 类模板,引入了一种非拥有(non-owning)、只读的"字符串视图" 的概念。
这个设计解决了在传统 C++ 中,为了传递只读字符串,我们不得不在 const std::string& 和 const char* 之间做出选择的困扰,它兼具了两者的优点,同时避免了它们的短板。
string_view 的核心思想是它本身并不分配、管理或复制任何字符数据,而是像一个轻量级的窗口,为你提供一个观察、访问一个已存在的、连续字符序列的视角。这个"窗口"在内部通常只由两个核心元素构成:
-
一个指向字符序列起始地址的指针 (
const CharT*)。 -
一个表示序列长度的整数值 (
size_t)。
正是这种设计,使得创建、拷贝和传递一个 string_view 的开销变得非常低,且不涉及任何堆内存分配。
接口
string_view 提供了与 std::string 高度相似的一套只读接口,这使得学习和迁移成本非常低。
| 类别 | 常用成员/接口 | 功能描述 |
|---|---|---|
| 核心观测 | data(), size(), empty(), length() |
获取底层数据的指针、视图长度,或判断是否为空视图。 |
| 元素访问 | operator[], at(), front(), back() |
以只读方式访问指定位置的字符或首尾字符,与 std::string 用法一致。 |
| 子串操作 | substr(pos, count) |
关键方法 。返回一个新的 string_view,其内容是调用者的一个子串。此操作是 O(1) 复杂度的,因为它只调整了新视图的指针和长度,完全不涉及字符数据的拷贝。 |
| 修改视图 | remove_prefix(n), remove_suffix(n) |
从视图的前端或后端"裁剪"掉 n 个字符,这同样通过移动指针和减小长度实现,开销为 O(1)。 |
| 查找与比较 | find(), rfind(), compare(), 关系运算符 (==, <, ...) |
提供了丰富的字符串查找和比较功能,其行为和 std::string 的同名方法一致。 |
| 构造与赋值 | 构造函数, operator= |
可从 const char*、std::string 等隐式构造,拷贝构造和赋值仅为浅拷贝,开销极低。 |
| 类型转换 | operator std::string() |
为了强调其非拥有性质,从 string_view 到 std::string 的转换是显式 (explicit) 的,防止不经意间的昂贵拷贝。 |
| 字面量支持 | operator""sv |
C++17 提供了字面量后缀,让你可以非常便捷地创建 string_view,如 "hello"sv。 |
注意事项
string_view 的强大之处在于它的"轻",而它的所有使用陷阱也源于它的"轻"。
-
🎯 核心陷阱:生命周期管理
string_view不拥有数据,因此它就像一块"借来"的手表,必须确保它指向的原始数据在其使用期间一直有效。cppstd::string_view sv; { std::string temp = "I am temporary"; sv = temp; // sv 现在指向 temp 的内部缓冲区 } // temp 被销毁,其内部缓冲区失效 std::cout << sv; // 严重错误:未定义行为 (悬垂指针)!核心原则 :永远不要返回指向局部
std::string变量的string_view,也避免将其存储在生命周期可能长于原始数据的地方。 -
🚫 不以空终止符 '\0' 结尾
与
std::string不同,string_view不保证其数据序列以空字符\0结尾。因此,绝对不能 将sv.data()直接传递给像printf、fopen这类依赖\0的 C 风格 API,除非你明确知道其来源是保证空终止的。 -
🔄 所有权与修改的界限
-
string_view是只读的,你无法通过它修改原始字符串。 -
当真正需要一份可以独立存在的、可修改的字符串拷贝时,务必显式地从
string_view构造一个std::string(例如std::string owner(sv)),由此产生的开销应当是你深思熟虑后的选择。
-
代码示例
cpp
#include <string_view>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
// ============================================================
// std::string_view --- C++17 引入的"字符串只读视图"
//
// 核心语义: 不拥有数据的指针+长度组合 (non-owning reference to string).
// 可以指向 string、C-string、字符数组、甚至子串,
// 全过程不分配、不拷贝内存。
//
// 与 const string& 的区别:
// - string_view 可以指向 C-string 而不构造临时 string
// - string_view 做 substr 是 O(1) (只调整指针), string 是 O(n) 拷贝
// - string_view 不保证 null-terminated
// ============================================================
// ============================================================
// 1. 构造: string_view 的所有创建方式
// ============================================================
void demo_construction() {
std::cout << "======== 1. 构造 ========\n";
// --- 1a. 默认构造 --- 空视图 ---
std::string_view sv_empty;
std::cout << "默认构造: size=" << sv_empty.size()
<< ", data()==nullptr? " << (sv_empty.data() == nullptr) << '\n';
// --- 1b. 从 C-string 构造 (隐式) ---
std::string_view sv_cstr = "hello world";
std::cout << "从C-string: " << sv_cstr << ", size=" << sv_cstr.size() << '\n';
// --- 1c. 从 C-string + 长度构造 (不需要 null 终止符!) ---
const char* raw = "one\0two\0three"; // 嵌入 '\0'
std::string_view sv_len(raw, 11);
std::cout << "从原始指针+长度: size=" << sv_len.size();
std::cout << ", 内容: ";
for (char c : sv_len) std::cout << (c == '\0' ? '_' : c);
std::cout << " (\\0 内部可见, 非 C-string 思维!)\n";
// --- 1d. 从 std::string 构造 (隐式转换) ---
std::string s = "std::string source";
std::string_view sv_str = s;
std::cout << "从string: " << sv_str << '\n';
// --- 1e. 拷贝构造 / 赋值 (浅拷贝, 只拷指针+长度) ---
std::string_view sv_copy = sv_cstr;
std::cout << "拷贝: " << sv_copy << " (与源共享同一块内存)\n";
// 没有移动构造的必要 --- string_view 本身已经是轻量级的
// --- 1f. operator""sv --- 字面量后缀 (C++17) ---
using namespace std::string_view_literals;
auto sv_lit = "literal string view"sv; // 类型: std::string_view
std::cout << "字面量 sv: " << sv_lit << ", type size=" << sizeof(sv_lit) << " bytes\n";
// sizeof(string_view) 通常 = sizeof(const char*) + sizeof(size_t) = 16 bytes
// --- 1g. 从字符数组构造 ---
char buf[] = {'a', 'b', 'c', 'd', 'e'};
std::string_view sv_buf(buf, sizeof(buf));
std::cout << "从字符数组: " << sv_buf << '\n';
}
// ============================================================
// 2. 元素访问: [] / at / front / back / data
// ============================================================
void demo_element_access() {
std::cout << "\n======== 2. 元素访问 ========\n";
std::string_view sv = "abcdefghij";
std::cout << "原始: " << sv << '\n';
// --- 2a. operator[] --- 不检查边界 (O(1)) ---
std::cout << "sv[0] = " << sv[0] << ", sv[5] = " << sv[5] << '\n';
// sv[99]; // 未定义行为! 不要这样做
// --- 2b. at() --- 越界时抛出 std::out_of_range ---
std::cout << "sv.at(3) = " << sv.at(3) << '\n';
try {
sv.at(99);
} catch (const std::out_of_range& e) {
std::cout << "sv.at(99) 抛出 out_of_range: " << e.what() << '\n';
}
// --- 2c. front() / back() --- 第一个和最后一个字符 ---
std::cout << "front() = " << sv.front() << '\n';
std::cout << "back() = " << sv.back() << '\n';
// 对空视图调用 front/back 是 UB
// --- 2d. data() --- 返回底层 const char* (不保证 null-terminated!) ---
std::cout << "data() = " << (void*)sv.data()
<< " (不保证有 \\0 结尾!)\n";
// 关键陷阱: string_view 的内部数据可能不以 '\0' 结尾!
// 如果需要 null-terminated string, 用 to_string() 或自己保证源数据有 '\0'
// 展示 data() 不保证 null-terminated:
std::string_view sub = sv.substr(0, 3); // "abc"
// sub.data() 指向 "abcdefghij" 的起始位置, sub 长度为 3
// 如果以 C-string 方式使用 sub.data(), 会读到 "abcdefghij"!
std::cout << "注意: sub = \"" << sub << "\", 但 data() 指向原串, "
<< "不要当成 C-string 用!\n";
}
// ============================================================
// 3. 容量: size / length / empty / max_size / npos
// ============================================================
void demo_capacity() {
std::cout << "\n======== 3. 容量 ========\n";
std::string_view sv = "hello";
// --- 3a. size() / length() --- 两者等价 ---
std::cout << "size() = " << sv.size() << '\n';
std::cout << "length() = " << sv.length() << " (与 size() 完全等价)\n";
// --- 3b. empty() --- 是否为空 ---
std::cout << "empty() = " << sv.empty() << '\n';
std::string_view sv_empty;
std::cout << "空视图.empty() = " << sv_empty.empty() << '\n';
// --- 3c. max_size() --- 理论上最大能表示的字符数 ---
std::cout << "max_size() = " << sv.max_size() << '\n';
// --- 3d. npos --- "不存在" 的哨兵值 ---
std::cout << "npos = " << std::string_view::npos
<< " (即 size_t 最大值)\n";
// find 系列函数找不到时返回 npos
}
// ============================================================
// 4. 修改器: remove_prefix / remove_suffix / swap
// ============================================================
void demo_modifiers() {
std::cout << "\n======== 4. 修改器 (不改变底层数据!) ========\n";
// --- 4a. remove_prefix(n) --- 向前移动起始指针 (O(1)) ---
std::string_view sv = "prefix_and_body";
std::cout << "原始: " << sv << " (size=" << sv.size() << ")\n";
sv.remove_prefix(7); // 去掉 "prefix_"
std::cout << "remove_prefix(7): " << sv << " (size=" << sv.size() << ")\n";
// 注意: 底层数据没变, 只是 start 指针前移了
// --- 4b. remove_suffix(n) --- 向前缩短尾部 (O(1)) ---
sv.remove_suffix(5); // 去掉 "_body"
std::cout << "remove_suffix(5): " << sv << " (size=" << sv.size() << ")\n";
// 结果只剩下 "and"
// --- 4c. 链式调用前缀/后缀裁剪 ---
auto trim = [](std::string_view sv) -> std::string_view {
// 去掉首尾空格 (注意: string_view 的 trim 不影响原数据)
while (!sv.empty() && sv.front() == ' ') sv.remove_prefix(1);
while (!sv.empty() && sv.back() == ' ') sv.remove_suffix(1);
return sv;
};
std::string_view padded_sv = " trimmed ";
std::cout << "trim 前: \"" << padded_sv << "\"\n";
std::cout << "trim 后: \"" << trim(padded_sv) << "\"\n";
// --- 4d. swap() --- 交换两个 view (O(1), 只交换指针+长度) ---
std::string_view a = "hello";
std::string_view b = "world";
a.swap(b);
std::cout << "a.swap(b) 后: a=" << a << ", b=" << b << '\n';
}
// ============================================================
// 5. 子串: substr (O(1)! 不拷贝数据)
// ============================================================
void demo_substr() {
std::cout << "\n======== 5. substr --- O(1) 子串 ========\n";
std::string_view sv = "0123456789abcdef";
// --- substr(pos, count) --- 返回新 view, 不拷贝 ---
auto sub1 = sv.substr(0, 5); // "01234"
auto sub2 = sv.substr(5, 3); // "567"
auto sub3 = sv.substr(8); // "89abcdef" (pos=8, 省略 count → 到末尾)
std::cout << "substr(0,5) = " << sub1 << '\n';
std::cout << "substr(5,3) = " << sub2 << '\n';
std::cout << "substr(8) = " << sub3 << '\n';
// 验证数据共享: 三个 view 的 data() 都指向同一块内存
std::cout << "sub1.data() - sv.data() = " << (sub1.data() - sv.data()) << " bytes\n";
std::cout << "sub2.data() - sv.data() = " << (sub2.data() - sv.data()) << " bytes\n";
std::cout << "sub3.data() - sv.data() = " << (sub3.data() - sv.data()) << " bytes\n";
std::cout << "(三个 view 指向同一块内存, 没有内存分配!)\n";
}
// ============================================================
// 6. 查找: find / rfind / find_first_of / find_last_of / find_first_not_of
// ============================================================
void demo_find() {
std::cout << "\n======== 6. 查找 (find 系列) ========\n";
std::string_view sv = "The quick brown fox jumps over the lazy dog";
// --- 6a. find(string_view, pos) --- 查找子串 ---
auto pos1 = sv.find("fox");
std::cout << "find(\"fox\") = " << pos1
<< " → sv[" << pos1 << "] = '" << sv[pos1] << "'\n";
// --- 6b. find(char, pos) --- 查找字符 ---
auto pos2 = sv.find('e');
std::cout << "find('e') = " << pos2
<< " → 第一个 'e' 在位置 " << pos2 << '\n';
// 从指定位置开始找:
auto pos3 = sv.find('e', pos2 + 1);
std::cout << "find('e', 3) = " << pos3 << " → 第二个 'e'\n";
// --- 6c. 未找到时返回 npos ---
auto pos_nf = sv.find("xyz");
std::cout << "find(\"xyz\") = " << pos_nf
<< " (== npos? " << (pos_nf == std::string_view::npos) << ")\n";
// --- 6d. rfind --- 反向查找 ---
auto last_e = sv.rfind('e');
std::cout << "rfind('e') = " << last_e << " → 最后一个 'e'\n";
// --- 6e. find_first_of --- 查找"集合中任意字符"首次出现 ---
auto first_vowel = sv.find_first_of("aeiou");
std::cout << "find_first_of(\"aeiou\") = " << first_vowel
<< " → 第一个元音 '" << sv[first_vowel] << "'\n";
// --- 6f. find_last_of --- 查找"集合中任意字符"最后一次出现 ---
auto last_digit = sv.find_last_of("0123456789");
std::cout << "find_last_of(\"0-9\") = " << last_digit
<< " (" << (last_digit == std::string_view::npos ? "没找到" : "找到") << ")\n";
// --- 6g. find_first_not_of --- 查找"不在集合中"的字符 ---
// 跳过前缀:
std::string_view hex = " 0xFF";
auto data_start = hex.find_first_not_of(" ");
std::cout << "hex 跳过空格: \"" << hex << "\" → 数据起始位置 " << data_start << '\n';
// --- 6h. find_last_not_of --- 查找尾部"不在集合中"的字符 ---
// 忽略尾随空格:
std::string_view with_trail = "data ";
auto data_end = with_trail.find_last_not_of(" ");
std::cout << "\"" << with_trail << "\" 有效长度: " << (data_end + 1) << '\n';
// --- 6i. 查找 const char* 版本 ---
auto pos_cstr = sv.find("brown", 0);
std::cout << "find(\"brown\") = " << pos_cstr << '\n';
}
// ============================================================
// 7. 比较: compare / 运算符
// ============================================================
void demo_comparison() {
std::cout << "\n======== 7. 比较 ========\n";
std::string_view a = "apple";
std::string_view b = "banana";
std::string_view c = "apple";
std::string_view d = "Apple";
// --- 7a. 比较运算符 (字典序比较) ---
std::cout << std::boolalpha;
std::cout << "apple == apple : " << (a == c) << '\n';
std::cout << "apple != banana : " << (a != b) << '\n';
std::cout << "apple < banana : " << (a < b) << '\n';
std::cout << "Apple < apple : " << (d < a)
<< " ('A' ascii=" << int('A') << " < 'a' ascii=" << int('a') << ")\n";
// --- 7b. compare() --- 6 种重载 ---
// 1: compare(string_view) --- 整体比较
int cmp1 = a.compare(b); // 负值 (a < b)
std::cout << "a.compare(b) = " << cmp1 << " (负=小于, 0=等于, 正=大于)\n";
// 2: compare(pos1, count1, string_view) --- 子串 vs 视图
std::string_view sv = "prefix_apple_suffix";
int cmp2 = sv.compare(7, 5, a); // sv[7..11]="apple" vs a="apple"
std::cout << "子串 vs 视图: sv[7..11]==\"apple\"? compare=" << cmp2 << '\n';
// 3: compare(pos1, count1, sv, pos2, count2) --- 子串 vs 子串
std::string_view sv2 = "xxxappleyyy";
int cmp3 = sv.compare(7, 5, sv2, 3, 5);
std::cout << "子串 vs 子串: compare=" << cmp3 << " (两个 \"apple\")\n";
// 4: compare(C-string) --- 与 C-string 比较
int cmp4 = a.compare("apricot");
std::cout << "\"apple\".compare(\"apricot\") = " << cmp4 << " (负: apple<apricot)\n";
// 5: compare(pos1, count1, C-string) --- 子串 vs C-string
std::cout << "sv[7..11].compare(\"apple\") = " << sv.compare(7, 5, "apple") << '\n';
// 6: compare(pos1, count1, C-string, count2) --- 子串 vs C-string+长度
const char* raw = "apple_extra";
std::cout << "sv[7..11].compare(\"apple_extra\", 5) = "
<< sv.compare(7, 5, raw, 5) << '\n';
}
// ============================================================
// 8. 与 string 互转: to_string / operator basic_string / copy
// ============================================================
void demo_conversion() {
std::cout << "\n======== 8. 与 string 互转 ========\n";
std::string_view sv = "sample text";
// --- 8a. 显式转换为 std::string (explicit operator std::string) ---
std::string s1(sv); // 直接构造
std::string s2 = std::string(sv); // 显式转换
std::cout << "显式转换: s1=" << s1 << ", s2=" << s2 << '\n';
// --- 8b. to_string() --- 创建 string 副本 (C++26 起支持自定义 Allocator) ---
// to_string() 在 C++17/20 中不存在! C++17 需用 std::string(sv)
// 这里用 std::string(sv) 替代
std::string s3(sv);
std::cout << "std::string(sv): " << s3 << '\n';
// --- 8c. copy(buf, n, pos) --- 拷贝字符到外部缓冲区 ---
char buf[8] = {};
sv.copy(buf, 6, 0); // 从 pos=0 开始拷贝 6 个字符
std::cout << "copy(buf, 6): buf = \"" << buf << "\"\n";
// 注意: copy 不会添加 '\0'! 需要手动处理
}
// ============================================================
// 9. 性能对比: string_view vs string 的 substr 开销
// ============================================================
void demo_performance() {
std::cout << "\n======== 9. string vs string_view 的 substr 性能 ========\n";
// string::substr() --- 总是拷贝 (O(n)):
std::string big_str(10000, 'x');
big_str[5000] = 'T'; // 目标字符在中间
// string_view::substr() --- 只调整指针 (O(1)):
std::string_view big_sv(big_str);
// 搜索 "T" 然后取周围子串:
auto pos = big_sv.find('T');
auto context = big_sv.substr(pos - 5, 11); // 5 个前后字符
std::cout << "搜索 'T' 位置 = " << pos << '\n';
std::cout << "周围上下文: \"" << context << "\" (没有拷贝 10000 字符!)\n";
// 等价于 string::substr (会拷贝):
// std::string context_s = big_str.substr(pos - 5, 11);
// string 版本会分配 11 字节 + 拷贝
}
// ============================================================
// 10. 实际应用场景
// ============================================================
// 场景 1: 函数参数 --- 接收任意字符串类型而零开销
// 可以接收: string, C-string, char array, string_view
void process_name(std::string_view name) {
if (name.empty()) {
std::cout << " (空名字)\n";
return;
}
std::cout << " Hello, " << name << "!\n";
}
// 场景 2: 分词器 --- 不拷贝地切分字符串
std::vector<std::string_view> split(std::string_view sv, char delim) {
std::vector<std::string_view> tokens;
size_t start = 0;
while (start <= sv.size()) {
auto end = sv.find(delim, start);
if (end == std::string_view::npos) {
tokens.push_back(sv.substr(start));
break;
}
tokens.push_back(sv.substr(start, end - start));
start = end + 1;
}
return tokens;
}
// 场景 3: 解析 CSV 行 (零拷贝)
void parse_csv_line(std::string_view line) {
auto fields = split(line, ',');
std::cout << " " << fields.size() << " 列: ";
for (auto f : fields) {
// 去除首尾空格
while (!f.empty() && f.front() == ' ') f.remove_prefix(1);
while (!f.empty() && f.back() == ' ') f.remove_suffix(1);
std::cout << '[' << f << "] ";
}
std::cout << '\n';
}
void demo_real_world() {
std::cout << "\n======== 10. 实际应用场景 ========\n";
// --- 场景 1: 通用字符串参数 ---
std::cout << "--- 通用参数 ---\n";
process_name("Alice"); // const char[6]
process_name(std::string("Bob")); // std::string
process_name(std::string_view("very long c-string").substr(0, 4)); // string_view, 无拷贝
process_name(""); // 空
// --- 场景 2: 零拷贝分词 ---
std::cout << "\n--- 零拷贝分词 ---\n";
std::string line = "hello world foo bar baz";
auto tokens = split(line, ' ');
std::cout << line << " → " << tokens.size() << " tokens:\n";
for (auto t : tokens) {
std::cout << " [" << t << "] (指向原串偏移 " << (t.data() - line.data()) << ")\n";
}
// --- 场景 3: CSV 解析 ---
std::cout << "\n--- CSV 解析 ---\n";
std::string csv = " Alice , Bob , Charlie , David ";
std::cout << "CSV: " << csv << '\n';
parse_csv_line(csv);
// --- 场景 4: 编译器词法分析 (经典场景) ---
std::cout << "\n--- 简单词法分析 ---\n";
const std::string source = "int main() { return 42; }";
std::string_view rest = source;
while (!rest.empty()) {
// 跳过空格
auto start = rest.find_first_not_of(" ");
if (start == std::string_view::npos) break;
rest.remove_prefix(start);
// 找到 token 末尾
auto end = rest.find_first_of(" ;(){}\r\n");
auto token = rest.substr(0, end);
std::cout << " token: \"" << token << "\"\n";
if (end == std::string_view::npos) break;
rest.remove_prefix(end + 1);
}
}
// ============================================================
// 11. 常见陷阱
// ============================================================
void demo_pitfalls() {
std::cout << "\n======== 11. 常见陷阱 ========\n";
// 陷阱 1: 悬垂引用 (dangling reference)
std::cout << "陷阱 1: 指向临时对象的悬垂引用:\n";
[[maybe_unused]] auto bad = []() -> std::string_view {
std::string temp = "temporary";
return temp; // ⊗ 危险! temp 被销毁, view 悬垂
};
// std::cout << bad(); // 未定义行为!
// 正确做法: 返回 string (拥有数据)
[[maybe_unused]] auto good = []() -> std::string {
std::string temp = "safe";
return temp; // ✓ 移动返回, 数据被移出
};
// 陷阱 2: data() 不一定 null-terminated
std::string_view sub = std::string_view("hello world").substr(0, 5); // "hello"
// sub.data() 不是 "hello\0" 而是 "hello world\0"
std::cout << "陷阱 2: sub.data() 不是独立 null-terminated 串\n sub = \""
<< sub << "\", 但 data() 长这样: \"" << sub.data() << "\"\n";
// 如果需要 C-string, 用 std::string(sub)
// 陷阱 3: remove_prefix/suffix 不检查越界 (UB!)
std::cout << "陷阱 3: remove_prefix 超过 size 是 UB\n";
std::string_view sv = "short";
sv.remove_prefix(3); // OK, sv = "rt"
sv.remove_prefix(1); // OK, sv = "t"
std::cout << " (sub, 但在安全范围内)\n";
// sv.remove_prefix(5); // UB! 只剩 3 字符却移了 5
// 陷阱 4: string_view 只是视图, 不延长生命周期
std::cout << "陷阱 4: string_view 不延长被指向对象的生命周期\n";
std::string_view dangling;
{
std::string inner = "inner scope";
dangling = inner; // ✓ 在此作用域内有效
} // inner 被销毁!
// std::cout << dangling; // UB! dangling 指向已销毁的对象
std::cout << " (dangling 现在指向已被销毁的 string)\n";
}
int main() {
demo_construction();
demo_element_access();
demo_capacity();
demo_modifiers();
demo_substr();
demo_find();
demo_comparison();
demo_conversion();
demo_performance();
demo_real_world();
demo_pitfalls();
std::cout << "\n所有演示完毕。\n";
return 0;
}
本期内容到这里就结束了。
封面图自取:
