C++17新特性

作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

前言

Qt 从版本 5.9 开始增加了对 C++17 的一些特性的支持,而在 Qt 5.12 及更高版本中,对 C++17 的支持更加完善。如果你使用的是 Qt 6 系列,那么它默认支持 C++17,因为 Qt 6 要求至少使用 C++17 标准

C++17 是 C++ 标准中的一个重要版本,带来了许多新的语言特性和标准库的增强。以下是 C++17 的一些主要新特性以及它们的用途和示例。

一. 语言特性

1. 更强大的 auto 关键字

在 C++17 之前,非类型模板参数只能是特定的类型,如整数类型、枚举类型、指针类型等。C++17 允许使用 auto 作为非类型模板参数,这使得模板更加灵活,可以接受更多类型的参数。

#include <iostream>

// 使用 auto 作为非类型模板参数
template <auto N>
struct MyStruct 
{
    static void print() {
        std::cout << "The value of N is: " << N << std::endl;
    }
};

int main() 
{
    // 实例化模板,N 为整数类型
    MyStruct<42>::print();

    // 实例化模板,N 为字符类型
    MyStruct<'A'>::print();

    return 0;
}

2. Lambda 表达式捕获 *this

在 C++ 中,lambda 表达式是一种非常便捷的方式用来创建匿名函数。在 C++11 和 C++14 中,lambda 表达式只能通过引用来捕获 this,这意味着你不能直接访问成员变量和成员函数,因为 this 指针是通过引用捕获的,而引用必须绑定到一个有效的对象上。
C++17 中引入了通过值捕获 this 的能力,这样即使在原始对象的生命周期结束后,在 lambda 表达式中仍然允许访问成员变量和成员函数。这在某些情况下非常有用,例如当你需要在异步操作中或者在对象已经被销毁后执行某些操作时。

#include <iostream>
#include <thread>
#include <vector>

class MyClass
{
public:
    MyClass(std::initializer_list<int> list) : data(list) {}

    // 启动一个线程,该线程将在对象被销毁后执行
    void startThread()
    {
        // std::thread t([this]() { // 对象被删除后,无法再访问成员变量 data
        std::thread t([*this]() {
            // 可以直接访问成员变量 data
            for (int num : data) {
                std::cout << num << std::endl;
            }
        });
        t.detach();
    }

private:
    std::vector<int> data;
};

int main() 
{
    MyClass *myObj = new MyClass({1, 2, 3, 4, 5});
    std::cout << "aaa" << std::endl;
    myObj->startThread();   // 启动线程
    delete myObj;           // 删除 myObj 对象
    myObj = nullptr;
    std::cout << "bbb" << std::endl;

    // 主线程等待3秒,验证输出内容
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 0;
}

3. 结构化绑定

结构化绑定允许解包结构体、数组、元组等,并将其成员直接绑定到变量上,使代码更加简洁。
使用结构化绑定将 getData 函数返回的 std::tuple 中的元素分别赋值给变量 i,d,s。

#include <tuple>
#include <iostream>

std::tuple<int, double, std::string> getData() 
{
    int a = 1 + 2;
    double b = 1.1 + 2.2;
    std::string c = "hello world";
    return {a, b, c};
}

int main()
{
    auto [i, d, s] = getData();	// 解包 tuple
    std::cout << i << ", " << d << ", " << s << std::endl;
}

4. 初始化语句

允许在 if 和 switch 语句中添加初始化语句,可以直接初始化一个变量作为条件表达式的一部分。增强了代码的局部性和可读性。

#include <iostream>

class MyObj
{
public:
    void playGame() { std::cout << "I like playing Switch !" << std::endl; }
};

int main()
{
    if (MyObj *obj = new MyObj(); !obj) {
        std::cout << "Failed to create object !" << std::endl;
    } else {
        obj->playGame();
    }

    return 0;
}

5. 折叠表达式

折叠表达式简化了可变参数模板中的参数展开操作,允许对参数包进行自动的运算折叠。

#include <iostream>

template<typename... Args>
auto sum(Args... args) {
    return (args + ...);
}

int main() 
{
    std::cout << sum(1, 2, 3, 4) << std::endl;
    return 0;
}

6. inline 变量

允许在头文件中定义内联变量,避免多重定义错误。在多个源文件中包含 header.h 时,不会出现多重定义错误

// header.h
#pragma once
inline int globalVar = 10;

二. 标准库特性

1. std::variant

可以存储多种不同类型的值,但同一时间只能存储其中一种类型的值,类似于联合类型,但更安全

#include <iostream>
#include <string>
#include <variant>

int main()
{
    // 指定 std::variant 可以持有的类型
    std::variant<int, double, std::string> v;
    v = 11;                 // v 现在是一个 int
    // v = 2.2;                // v 现在是一个 double
    // v = "hello world";      // v 现在是一个 std::string

    // 使用 std::get 获取数据,需要知道当前的数据类型。如果类型不正确,则抛出异常 std::bad_variant_access
    try {
        auto a = std::get<int>(v);
        std::cout << "get value: " << a << std::endl;
    } catch (const std::bad_variant_access& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    // 使用 std::visit 输出数据
    std::visit([](auto&& arg) {
        // 直接输出数据,不需要知道类型
        // std::cout << arg << std::endl;

        // 通过 std::is_same_v 判断类型是否相同,做一些特殊处理
        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 << "std::string: " << arg << std::endl;
        }
    }, v);

    // 使用 std::get_if 获取指向存储值的指针,如果类型不匹配则返回 nullptr
    if ( auto p = std::get_if<int>(&v) ) {
        // v 包含一个 int,p 指向该值
        std::cout << "int: " << *p << std::endl;
    } else {
        // v 不包含 int
        std::cout << "Does not include int type !" << std::endl;
    }
}

2. std::optional

表示一个可能存在或不存在的值,避免使用空指针或特殊值来表示无效状态

#include <iostream>
#include <optional>

std::optional<int> divide(int a, int b) 
{
	// 如果除数不为零,则返回计算结果;否则返回 std::nullopt 表示无效结果
    if (b != 0) {
        return a / b;
    }
    return std::nullopt;
}

int main() 
{
    auto result = divide(10, 2);
    if (result) {
        std::cout << "Result: " << *result << std::endl;
    } else {
        std::cout << "Division by zero" << std::endl;
    }
    return 0;
}

3. std::any

提供类型擦除功能,可以存储任意类型的值,并且可以在运行时查询和访问存储的值的类型

#include <iostream>
#include <any>

int main() 
{
    std::any a = 10;
    if (a.type() == typeid(int)) {
        std::cout << std::any_cast<int>(a) << std::endl;
    }
    return 0;
}

4. std::string_view

提供了一种轻量级的、不拥有数据的字符串类,它允许更高效的字符串操作。
由于 std::string_view 不拥有字符串数据,因此要确保在使用 std::string_view 时,其所指向的字符串数据仍然有效。std::string_view 是只读的,不能直接修改其所指向的字符串数据。如果需要修改字符串,应该使用 std::string

#include <iostream>
#include <string_view>

int main() 
{
    // 从 C 风格字符串构造
    const char* cstr = "Hello";
    std::string_view sv1(cstr);

    // 从 std::string 构造
    std::string str = "World";
    std::string_view sv2(str);

    // 直接使用字符串字面量构造
    std::string_view sv3 = "C++17";

    std::cout << sv1 << " " << sv2 << " " << sv3 << std::endl;
    return 0;
}

5. std::filesystem

C++17 增加了标准文件系统库,提供了文件操作、目录遍历等功能。提供了跨平台的文件系统操作接口,方便进行文件和目录的管理。

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() 
{
    fs::path p = "./test.txt";
    if (fs::exists(p)) {
        std::cout << "File exists" << std::endl;
    }
    return 0;
}

6. 并行算法

<algorithm>头文件中添加了并行版本的算法,如 std::for_each、std::transform 等,可以利用多核处理器提高算法的执行效率。
std::for_each 的第一个参数 std::execution::par 表示使用并行执行策略。

#include <iostream>
#include <vector>
#include <algorithm>
#include <execution>

int main()
{
    std::vector<int> v(1000);
    std::iota(v.begin(), v.end(), 0);	// 在范围中生成递增的整数序列
    std::for_each(std::execution::par, v.begin(), v.end(), [](int& x) {
        x *= 2;
    });

	// 输出数据
    for (int num : v) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

三. 其他改进

1. constexpr if

在编译时进行更多的计算和决策,从而减少运行时的开销。if constexpr 允许在编译时进行条件判断,根据条件选择不同的代码路径,避免运行时开销,提高程序的执行效率

#include <iostream>

template <typename T>
auto getValue(T value) 
{
    if constexpr (std::is_integral_v<T>) {
        return value * 2;
    } else {
        return value;
    }
}

int main() 
{
    std::cout << getValue(5) << std::endl;
    std::cout << getValue(3.14) << std::endl;
    return 0;
}

2. constexpr Lambda

C++17 允许 lambda 函数成为 constexpr,如果它们满足条件,就可以在需要编译时评估的上下文中使用。

constexpr auto lambda = [](int x) { return x * 2; };
static_assert(lambda(5) == 10);

3. 嵌套命名空间

简化多层命名空间的写法

// 传统写法
namespace A
{
    namespace B
    {
        namespace C
        {

        };
    };
};

// c++ 17 新写法
namespace A::B::C
{

};

4. 宏 __has_include

用来判断有没有包含某文件

int main() 
{
    #if __has_include(<vector>)
    	std::cout << "<vector> has included" << std::endl;
    #endif
    
    #if __has_include("istream")
        std::cout << "iostream has included" << std::endl;
    #endif

    return 0;
}

5. 字面量改进

C++17 增强了字面量,包括对整数和浮点字面量的改进,以及对真和假字面量的支持。

auto num = 123_456; 	// Underscore in integer literals  
auto pi = 3.1415_f; 	// Suffix for floating-point literals
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值