23、【C++】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库,极大简化了日常开发任务。合理应用这些特性可使代码更高效、更易维护。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐