23、【C++】C++17常用特性
结构化绑定可将tuple或pair的多个返回值绑定到变量:1.1.2 绑定结构体/数组元素绑定自定义结构体或数组:1.1.3 绑定到引用与const限定通过绑定引用,绑定常量:1.2 if constexpr(编译期条件判断)1.2.1 在模板中根据类型分支在编译期根据模板参数选择代码分支,未选中分支不生成代码:1.2.2 替代SFINAE的简单场景可替代部分SFINAE(Substitution
23、【C++】C++17常用特性
目录
- 一、核心语言特性
- 二、标准库新增特性
- 三、C++17特性应用场景
- 四、编译器支持与迁移策略
一、核心语言特性
1.1 结构化绑定(Structured Bindings)
1.1.1 绑定tuple/pair的成员
结构化绑定可将tuple或pair的多个返回值绑定到变量:
#include <tuple>
#include <string>
int main() {
auto [id, name, score] = std::make_tuple(1, std::string("Alice"), 95.5);
// id=1, name="Alice", score=95.5
return 0;
}
1.1.2 绑定结构体/数组元素
绑定自定义结构体或数组:
struct Point { int x; int y; };
Point p = {10, 20};
// 绑定结构体成员
auto [x, y] = p; // x=10, y=20
// 绑定数组元素
int arr[] = {1, 2, 3};
auto [a, b, c] = arr; // a=1, b=2, c=3
1.1.3 绑定到引用与const限定
通过auto&绑定引用,const auto绑定常量:
// 绑定引用(可修改原对象)
auto& [rx, ry] = p;
rx = 100; // p.x变为100
// 绑定常量(不可修改)
const auto [cx, cy] = p;
// cx = 200; // 错误:const变量不可修改
1.2 if constexpr(编译期条件判断)
1.2.1 在模板中根据类型分支
if constexpr在编译期根据模板参数选择代码分支,未选中分支不生成代码:
template <typename T>
auto get_value(T t) {
if constexpr (std::is_integral_v<T>) {
return t + 1; // 整数类型分支
} else if constexpr (std::is_floating_point_v<T>) {
return t * 1.5; // 浮点类型分支
} else {
return t; // 其他类型分支
}
}
int main() {
int i = get_value(2); // 3(整数分支)
double d = get_value(2.0); // 3.0(浮点分支)
std::string s = get_value(std::string("a")); // "a"(其他分支)
return 0;
}
1.2.2 替代SFINAE的简单场景
if constexpr可替代部分SFINAE(Substitution Failure Is Not An Error)逻辑,更简洁:
// SFINAE方式(C++11/14)
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void func(T t) {}
// if constexpr方式(C++17,更简洁)
template <typename T>
void func(T t) {
if constexpr (std::is_integral_v<T>) {
// 整数类型处理
} else {
static_assert(std::is_integral_v<T>, "T must be integral");
}
}
1.3 折叠表达式(Fold Expressions)
1.3.1 一元折叠与二元折叠
折叠表达式用于展开参数包,支持四种操作:
- 一元左折叠:
(pack op ...) - 一元右折叠:
(... op pack) - 二元左折叠:
(pack op ... op init) - 二元右折叠:
(init op ... op pack)
// 一元右折叠:计算参数包之和(args1 + args2 + ... + argsN)
template <typename... Args>
auto sum(Args... args) {
return (... + args); // 等价于args1 + args2 + ... + argsN
}
// 二元左折叠:带初始值0的和(0 + args1 + ... + argsN)
template <typename... Args>
auto sum_with_init(Args... args) {
return (0 + ... + args);
}
1.3.2 展开参数包(替代递归)
C++17前需递归展开参数包,C++17可用折叠表达式简化:
// C++11/14:递归展开
void print() {} // 递归终止函数
template <typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...);
}
// C++17:折叠表达式展开
template <typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // 注意运算符优先级
}
1.3.3 与初始化列表结合使用
折叠表达式可与初始化列表结合,实现参数包的批量操作:
template <typename... Args>
void print(Args... args) {
(void)std::initializer_list<int>{(std::cout << args << " ", 0)...};
}
1.4 内联变量(Inline Variables)
1.4.1 头文件中的常量定义
内联变量允许在头文件中定义全局变量,避免多重定义错误:
// constants.h
inline constexpr int MAX_SIZE = 1024; // 内联变量,可在多个文件中包含
inline const std::string VERSION = "1.0.0";
1.4.2 替代宏定义与静态成员变量
内联变量类型安全,可替代宏和静态成员变量:
// 替代#define MAX 100(宏无类型检查)
inline constexpr int MAX = 100;
// 替代类静态成员变量(无需在cpp中定义)
class Config {
public:
inline static constexpr int TIMEOUT = 30;
};
1.5 初始化if/switch(Init Statements)
1.5.1 if条件中声明变量
在if条件中声明变量,变量作用域限定在if块内:
#include <map>
std::map<int, std::string> map = {{1, "one"}, {2, "two"}};
// C++17前:需在外部声明变量
auto it = map.find(1);
if (it != map.end()) {
std::cout << it->second << std::endl;
}
// C++17:条件中初始化
if (auto it = map.find(1); it != map.end()) {
std::cout << it->second << std::endl; // 作用域仅限于if块
}
1.5.2 switch条件中声明变量
类似if,switch条件中也可声明变量:
switch (auto val = get_value(); val) {
case 1: /* 处理1 */ break;
case 2: /* 处理2 */ break;
default: /* 处理默认 */
}
1.6 模板参数推导增强
1.6.1 类模板参数自动推导
C++17允许类模板像函数模板一样自动推导参数类型:
std::pair p(1, "hello"); // C++17:自动推导为std::pair<int, const char*>
std::tuple t(1, 2.0, "three"); // 自动推导为std::tuple<int, double, const char*>
1.6.2 构造函数模板的参数推导
类的构造函数模板也支持参数推导:
template <typename T>
struct MyContainer {
MyContainer(T t) {} // 构造函数模板
};
MyContainer mc(10); // 推导T为int
1.7 属性与特性增强
1.7.1 [[nodiscard]]:禁止忽略返回值
[[nodiscard]]标记函数,若调用者忽略返回值,编译器发出警告:
[[nodiscard]] int compute() { return 42; }
int main() {
compute(); // 警告:忽略[[nodiscard]]函数的返回值
return 0;
}
1.7.2 [[maybe_unused]]:标记未使用实体
[[maybe_unused]]标记可能未使用的变量或参数,避免编译器警告:
void func([[maybe_unused]] int unused_param) {
[[maybe_unused]] int unused_var = 10;
// 未使用上述变量,编译器不警告
}
二、标准库新增特性
2.1 文件系统库(std::filesystem)
2.1.1 路径处理(std::filesystem::path)
std::filesystem::path封装路径操作,支持跨平台路径格式:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path p("dir/file.txt");
std::cout << "文件名:" << p.filename() << std::endl; // "file.txt"
std::cout << "父目录:" << p.parent_path() << std::endl; // "dir"
std::cout << "绝对路径:" << fs::absolute(p) << std::endl;
return 0;
}
2.1.2 目录遍历与文件操作
遍历目录并创建文件:
// 遍历目录
for (const auto& entry : fs::directory_iterator(".")) {
if (entry.is_regular_file()) {
std::cout << entry.path() << std::endl; // 打印普通文件路径
}
}
// 创建目录
fs::create_directory("new_dir");
// 拷贝文件
fs::copy_file("src.txt", "dest.txt", fs::copy_options::overwrite_existing);
// 删除文件
fs::remove("old.txt");
2.1.3 文件属性与权限
获取文件大小、修改时间等属性:
fs::path p("file.txt");
if (fs::exists(p)) {
std::cout << "大小:" << fs::file_size(p) << "字节" << std::endl;
auto ftime = fs::last_write_time(p); // 文件修改时间
}
2.2 std::optional:可选值类型
2.2.1 基本用法与value()/value_or()
std::optional表示可能为空的值,避免使用nullptr或特殊值:
#include <optional>
std::optional<int> find(int key) {
if (key == 42) {
return 42; // 返回有效值
} else {
return std::nullopt; // 返回空值
}
}
int main() {
auto opt = find(42);
if (opt.has_value()) {
std::cout << opt.value() << std::endl; // 42
}
std::cout << opt.value_or(-1) << std::endl; // 42(非空)
auto opt2 = find(0);
std::cout << opt2.value_or(-1) << std::endl; // -1(为空)
return 0;
}
2.2.2 替代nullptr与特殊值
std::optional比nullptr更类型安全,比特殊值(如-1)更明确:
// 传统方式:用-1表示未找到(不直观)
int find_old(int key) { return key == 42 ? 42 : -1; }
// C++17:用optional明确表示可能为空
std::optional<int> find_new(int key) { return key == 42 ? 42 : std::nullopt; }
2.3 std::variant:类型安全的联合体
2.3.1 存储与访问不同类型
std::variant可存储多种类型中的一种,编译期检查类型安全:
#include <variant>
#include <string>
int main() {
std::variant<int, double, std::string> var;
var = 10; // 存储int
var = 3.14; // 存储double
var = "hello"; // 存储string
// 访问方式1:get_if
if (auto p = std::get_if<std::string>(&var)) {
std::cout << *p << std::endl; // "hello"
}
// 访问方式2:visit(推荐)
std::visit([](auto&& val) {
std::cout << val << std::endl;
}, var);
return 0;
}
2.3.2 访问器(std::visit)
std::visit接受variant和访问器(如lambda),根据存储类型调用对应处理逻辑:
using Var = std::variant<int, double, std::string>;
Var v = 3.14;
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "int: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "double: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "string: " << arg << std::endl;
}
}, v); // 输出:double: 3.14
2.4 std::any:任意类型容器
2.4.1 存储与类型检查
std::any可存储任意类型的值,运行时获取类型信息:
#include <any>
#include <typeinfo>
int main() {
std::any a = 10;
a = std::string("hello");
if (a.type() == typeid(std::string)) {
std::cout << std::any_cast<std::string>(a) << std::endl; // "hello"
}
try {
std::any_cast<int>(a); // 类型不匹配,抛出std::bad_any_cast
} catch (const std::bad_any_cast& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
2.4.2 与variant的区别
| 特性 | std::variant | std::any |
|---|---|---|
| 类型限制 | 编译期固定类型集合 | 无限制,任意类型 |
| 类型安全 | 编译期检查 | 运行时检查(抛出异常) |
| 内存 | 栈上存储(小类型) | 大类型可能在堆上分配 |
| 用途 | 已知类型集合的选择(如状态机) | 完全动态类型(如插件系统) |
2.5 std::string_view:非所有权字符串引用
2.5.1 避免字符串拷贝
std::string_view是字符串的非所有权引用,避免不必要的字符串拷贝:
#include <string_view>
// 接受string_view,避免传递const std::string&的拷贝
void print_str(std::string_view sv) {
std::cout << sv << std::endl;
}
int main() {
std::string s = "hello";
print_str(s); // 传递string_view,无拷贝
print_str("world"); // 直接接受字符串字面量
print_str(s.substr(0, 3)); // 传递子串视图,无拷贝
return 0;
}
2.5.2 注意事项(生命周期管理)
string_view不管理字符串生命周期,需确保指向的字符串有效:
std::string_view get_view() {
std::string s = "temporary";
return std::string_view(s); // 危险:返回临时对象的视图
}
int main() {
auto sv = get_view(); // sv指向已销毁的字符串,访问UB
return 0;
}
2.6 并行算法
2.6.1 执行策略(std::execution)
C++17算法支持三种执行策略:
std::execution::seq:顺序执行。std::execution::par:并行执行(多线程)。std::execution::par_unseq:并行且向量化执行。
2.6.2 并行for_each示例
#include <algorithm>
#include <execution>
#include <vector>
int main() {
std::vector<int> v(10000);
// 并行初始化数组
std::for_each(std::execution::par, v.begin(), v.end(), [](int& x) {
x = rand();
});
return 0;
}
三、C++17特性应用场景
3.1 结构化绑定遍历map
遍历std::map时直接绑定键值对:
std::map<int, std::string> map = {{1, "one"}, {2, "two"}};
for (const auto& [key, value] : map) {
std::cout << key << ": " << value << std::endl;
}
3.2 optional处理可能失败的函数
用optional包装可能失败的操作(如文件打开):
std::optional<fs::path> find_file(const fs::path& dir, const std::string& name) {
for (const auto& entry : fs::directory_iterator(dir)) {
if (entry.is_regular_file() && entry.path().filename() == name) {
return entry.path();
}
}
return std::nullopt;
}
3.3 filesystem实现目录拷贝
使用filesystem库递归拷贝目录:
void copy_dir(const fs::path& src, const fs::path& dest) {
fs::create_directories(dest);
for (const auto& entry : fs::directory_iterator(src)) {
auto dest_path = dest / entry.path().filename();
if (entry.is_directory()) {
copy_dir(entry.path(), dest_path);
} else {
fs::copy_file(entry.path(), dest_path);
}
}
}
3.4 string_view优化字符串处理
在解析协议或日志时,用string_view避免多次字符串拷贝:
// 解析"key=value"格式,返回键值对视图
std::pair<std::string_view, std::string_view> parse_kv(std::string_view s) {
auto pos = s.find('=');
return {s.substr(0, pos), s.substr(pos + 1)};
}
四、编译器支持与迁移策略
4.1 主流编译器支持情况
- GCC:7.1+支持大部分C++17特性。
- Clang:5.0+支持大部分C++17特性。
- MSVC:Visual Studio 2017+支持大部分C++17特性。
4.2 混合C++17与旧标准代码
- 逐步迁移:优先在新代码中使用C++17特性,旧代码保持不变。
- 条件编译:对编译器不支持的特性使用条件编译:
#if __cplusplus >= 201703L // C++17代码 #else // 旧标准替代实现 #endif
以上内容,全面覆盖了C++17的核心语言特性和标准库新增功能。C++17显著提升了代码的简洁性和表达能力,尤其是结构化绑定、折叠表达式和filesystem库,极大简化了日常开发任务。合理应用这些特性可使代码更高效、更易维护。
更多推荐



所有评论(0)