【C++11、C++14、C++17、C++20新特性整理总结】

C++11、C++14、C++17、C++20新特性整理总结

提示:自用


文章目录


前言

C++11、C++14、C++17、C++20新特性整理总结。


一、C++11是什么,C++11标准的由来?

  • 2011 年,新的 C++ 11 标准诞生,用于取代 C++ 98 标准。此标准还有一个别名,为“C++ 0x”;
  • 2014 年,C++ 14 标准发布,该标准库对 C++ 11 标准库做了更优的修改和更新;
  • 2017 年底,C++ 17 标准正式颁布。

所谓标准,即明确 C++ 代码的编写规范,所有的 C++ 程序员都应遵守此标准。

C++11特性

1.简化使用

1.1 C++11 auto & decltype

auto

在C++11之前的版本中,定义变量或者声明变量之前都需要指明类型。为了简化使用,C++11使用auto关键字来支持自动类型推导

auto a = 10; // 10是int型,可以自动推导出a是int
auto f = 12.8;
auto p = &a;
auto url = “http://c.biancheng.net/cplus/”;
  • 第 1 行中,10 是一个整数,默认是 int 类型,所以推导出变量 a 的类型是 int。
  • 第 2 行中,12.8 是一个小数,默认是 double 类型,所以推导出变量 f 的类型是 double。
  • 第 3 行中,&a 的结果是一个 int* 类型的指针,所以推导出变量 p 的类型是 int*。
  • 第 4 行中,由双引号""包围起来的字符串是 const char* 类型,所以推导出变量 url 的类型是 const char*,也即一个常量指针。

auto 与 const 结合的用法:

  • 当类型不为引用时,auto 的推导结果将不保留表达式的 const 属性;
  • 当类型为引用时,auto 的推导结果将保留表达式的 const 属性。

auto 的限制:

  • auto 不能在函数的参数中使用。
  • auto 不能作用于类的非静态成员变量(也就是没有 static 关键字修饰的成员变量)中。
  • auto 关键字不能定义数组。
  • auto 不能作用于模板参数
decltype

decltype 是 C++11 新增的一个关键字,它和 auto 的功能一样,都用来在编译时期进行自动类型推导。因为 auto 并不适用于所有的自动类型推导场景,在某些特殊情况下 auto 用起来非常不方便,甚至压根无法使用,所以 decltype 关键字也被引入到 C++11 中。相对于auto用于推导变量类型,而decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。

decltype(exp) varname = value;
decltype(exp) varname;

decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。

int a = 0;
decltype(a) b = 1;  //b 被推导成了 int
decltype(10.8) x = 5.5;  //x 被推导成了 double
decltype(x + 100) y;  //y 被推导成了 double

decltype推导规则:

对于decltype(exp)有

  • exp是表达式,decltype(exp)和exp类型相同
  • exp是函数调用,decltype(exp)和函数返回值类型相同
  • 其它情况,若exp是左值,decltype(exp)是exp类型的左值引用

auto 与 decltype的区别:

  • auto用于变量的类型推导,根据初始化表达式的类型来推导变量的类型,常用于简化代码和处理复杂类型。而decltype则用于获取表达式的类型,保留修饰符,并且可以进行表达式求值。
  • auto在初始化时进行类型推导,而decltype直接查询表达式的类型,可以用于任何表达式,包括没有初始化的变量。
  • auto在编译期间确定类型,并且无法更改。而decltype在运行时才确定表达式的类型。
  • auto适用于简单的类型推导,而decltype适用于复杂的类型推导和获取表达式的结果类型。

1.2 C++11 for循环

for (declaration : expression){
//循环体
}
//实例:
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    for (auto number : numbers) {
        std::cout << number << " ";
    }

    return 0;
}
  • declaration:表示此处要定义一个变量,该变量的类型为要遍历序列中存储元素的类型。需要注意的是,C++ 11 标准中,declaration参数处定义的变量类型可以用 auto 关键字表示,该关键字可以使编译器自行推导该变量的数据类型。
  • expression:表示要遍历的序列,常见的可以为事先定义好的普通数组或者容器,还可以是用 {} 大括号初始化的序列。

1.3 C++11 资源管理指针(智能指针)

C++11中,引入新的智能指针类,用于更安全和方便地管理动态分配的资源,避免内存泄漏和悬空指针等问题。

unique_ptr 独占智能指针

每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收。
C++11神器之智能指针

#include <memory>
//unique_ptr的创建
std::unique_ptr<int> p1();
std::unique_ptr<int> p2(nullptr);

std::unique_ptr<int> p4(new int);
std::unique_ptr<int> p5(p4);//错误,堆内存不共享
std::unique_ptr<int> p5(std::move(p4));//正确,调用移动构造函数

std::unique_ptr<int> ptr = std::make_unique<int>(42); //通过 make_unique 函数可以创建
shared_ptr 共享式智能指针
  • std::shared_ptr 是一种共享式智能指针,多个指针可以同时共享对同一对象的拥有权
  • std::shared_ptr 使用引用计数技术追踪所管理对象的引用数量,当引用计数变为零时,自动销毁所管理的对象。
  • 通过 std::make_shared 函数可以创建 std::shared_ptr 对象
#include <memory>
//shared_ptr的创建
std::shared_ptr<int> p1;//不传入任何实参
std::shared_ptr<int> p2(nullptr);//传入空指针 nullptr
std::shared_ptr<int> p3(new int(10));//可以明确其指向
std::shared_ptr<int> p3 = std::make_shared<int>(10);//通过 make_shared 函数可以创建
weak_ptr 弱引用智能指针
  • std::weak_ptr 是一种弱引用智能指针,它可以解决 std::shared_ptr 的循环引用问题。
  • std::weak_ptr 指向 std::shared_ptr 管理的对象,但不会增加引用计数。因此,当所有 std::shared_ptr 对象超出作用域后,即使还有 std::weak_ptr 对象存在,所管理的对象也会被销毁。
  • 通过 std::shared_ptr 的 std::weak_ptr 构造函数可以创建 std::weak_ptr 对象
#include <memory>
//shared_ptr的创建
std::weak_ptr<int> wp1;//不传入任何实参
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);//可以明确其指向

1.4 C++11 统一初始化

为了统一初始化方式,并且让初始化行为具有确定的效果,C++11 中提出了列表初始化(List-initialization)的概念。可以通过多种方式来初始化对象,无论是基本类型、类类型还是数组都可以使用相同的语法。

int x = 42;  // 直接初始化一个整数
std::string s("Hello");  // 直接初始化一个字符串对象

int y = x;  // 使用拷贝初始化将 x 的值赋给 y
std::vector<int> v = {1, 2, 3};  // 使用拷贝初始化进行向量初始化

int z{123};  // 使用列表初始化一个整数
std::vector<int> nums{1, 2, 3};  // 使用列表初始化初始化一个向量
std::pair<int, double> p{42, 3.14};  // 使用列表初始化初始化一个键值对

1.5 C++11 nullptr 初始化空指针

实际开发中,避免产生**“野指针”**最有效的方法,就是在定义指针的同时完成初始化操作,即便该指针的指向尚未明确,也要将其初始化为空指针。

所谓“野指针”,又称“悬挂指针”,指的是没有明确指向的指针。野指针往往指向的是那些不可用的内存区域,这就意味着像操作普通指针那样使用野指针(例如 &p),极可能导致程序发生异常。

nullptr 是 nullptr_t 类型的右值常量,专用于初始化空类型指针。nullptr_t 是 C++11 新增加的数据类型,可称为“指针空值类型”。也就是说,nullpter 仅是该类型的一个实例对象(已经定义好,可以直接使用),如果需要我们完全定义出多个同 nullptr 完全一样的实例对象。

值得一提的是,nullptr 可以被隐式转换成任意的指针类型。举个例子:

int * a1 = nullptr;
char * a2 = nullptr;
double * a3 = nullptr;

显然,不同类型的指针变量都可以使用 nullptr 来初始化,编译器分别将 nullptr 隐式转换成 int*、char* 以及 double* 指针类型。

1.6 C++11 constexpr 验证是否为常量表达式

C++11 constexpr 能有啥用?
constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。常量表达式(const experssion)是指:

  • 值不会改变
  • 在编译过程就能得到计算结果的表达式。

constexpr和const的区别:
两者都代表可读,const只表示read only的语义,只保证了运行时不可以被修改,但它修饰的仍然有可能是个动态变量,而constexpr修饰的才是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变,constexpr可以用于修饰函数,这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理。

#include<iostream>
using namespace std;

constexpr int func(int i) {
    return i + 1;
}

int main() {
    int i = 2;
    func(i);// 普通函数
    func(2);// 编译期间就会被计算出来
}

1.7 C++11 explicit

explicit关键字作用
explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换,根据代码看explicit的作用:

struct A {
    explicit A(int value) {
        cout << "value" << endl;
    }
};

int main() {
    A a = 1; // error,不可以隐式转换
    A aa(2); // ok
    return 0;
}

1.8 C++11 final & override

c++11关于继承新增了两个关键字,final用于修饰一个,表示禁止该类进一步派生和虚函数的进一步重载,override用于修饰派生类中的成员函数标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误。

struct Base {
    virtual void func() {
        cout << "base" << endl;
    }
};

struct Derived : public Base{
    void func() override { // 确保func被重写
        cout << "derived" << endl;
    }

    void fu() override { // error,基类没有fu(),不可以被重写
        
    }
};

1.9 C++11 右值引用

每天5分钟了解现代C++新特性 - 第6章 右值引用

左值、右值:

  • 可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。
  • 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。

C++ 中的右值引用(Rvalue reference)是一种引用类型,它用于绑定到临时对象或将要被移动的对象(右值)。通过右值引用,我们可以对右值进行有效的操作,如移动语义和完美转发。右值引用的语法是在类型后面加上 &&,例如 int&& 表示一个右值引用到 int 类型的对象。右值引用只能绑定到右值,不能绑定到左值。

1.10 C++11 移动语义

C++11 引入了移动语义(Move Semantics)的概念,旨在提高对象的性能和效率。移动语义通过转移资源所有权,避免不必要的拷贝操作,从而更高效地管理对象。
移动语义通过引入右值引用(Rvalue Reference)来解决这个问题。右值引用使用 && 语法进行声明,表示一个临时对象或者即将销毁的对象。在移动语义中,我们可以将资源的所有权从一个对象转移到另一个对象,而不需要进行昂贵的拷贝操作。在使用移动语义时,可以借助 std::move 函数将左值转换为右值引用,以便进行移动操作。下面是一个简单的示例:

#include <iostream>
#include <vector>

class MyObject {
public:
    MyObject() {
        std::cout << "Default constructor" << std::endl;
        // 假设需要分配大量内存或进行其他资源密集型操作
    }

    MyObject(const MyObject& other) {
        std::cout << "Copy constructor" << std::endl;
        // 实现对象的拷贝操作
    }

    MyObject(MyObject&& other) {
        std::cout << "Move constructor" << std::endl;
        // 实现对象的移动操作
    }
};

int main() {
    MyObject obj1;  // 调用默认构造函数

    MyObject obj2(obj1);  // 调用拷贝构造函数,拷贝 obj1 的值到 obj2
    MyObject obj3(std::move(obj1));  // 调用移动构造函数,将 obj1 的值转移到 obj3

    return 0;
}

1.11 C++11 完美转发

完美转发(perfect forwarding)是 C++ 中用于保持传递参数类型和转发函数调用的机制。它通常与模板和右值引用一起使用,以实现泛型编程中的参数传递。

  • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
  • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。

2 并发支持

2.1 C++11 内存模型

C++11引入了一个新的内存模型,即C++11内存模型(C++11 memory model)。它定义了多线程并发环境下对共享数据的访问和修改行为,以及对原子操作和同步操作的语义。在这些之上,C++11还提供了对原子类型和无锁编程的支持,并且与之集成。
C++11内存模型确保了一些基本的原则:

  • 原子性(Atomicity):对于原子类型(std::atomic),其成员函数的操作是原子的,不会被其他线程中断。
  • 可见性(Visibility):对于非原子类型,通过使用互斥量或同步操作来确保共享数据的可见性,即在一个线程中对共享数据的修改会立即反映到其他线程中。
  • 有序性(Ordering):通过同步操作(如互斥量、原子操作的memory_order参数等)来定义操作的顺序性,从而在多线程环境中确定操作和事件的相对顺序。

2.2 C++11 线程与锁

C++多线程+线程池

c++11引入了std::thread来创建线程,支持对线程join或者detach。

#include <iostream>
#include <thread>
 
using namespace std;
 
int main() {
    auto func = []() {
        for (int i = 0; i < 10; ++i) {
            cout << i << " ";
        }
        cout << endl;
    };
    std::thread t(func);
    if (t.joinable()) {
        t.detach();
    }
    auto func1 = [](int k) {
        for (int i = 0; i < k; ++i) {
            cout << i << " ";
        }
        cout << endl;
    };
    std::thread tt(func1, 20);
    if (tt.joinable()) { // 检查线程可否被join
        tt.join();
    }
    return 0;
}

std::mutex是一种线程同步的手段,用于保存多线程同时操作的共享数据。

  • std::mutex:独占的互斥量,不能递归使用,不带超时功能
  • std::recursive_mutex:递归互斥量,可重入,不带超时功能
  • std::timed_mutex:带超时的互斥量,不能递归
  • std::recursive_timed_mutex:带超时的互斥量,可以递归使用

C++11主要有两种RAII方式的锁封装,可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁。std::lock_guard和std::unique_lock两种方式,使用方式都类似。

2.3 C++11 期值(future)

  • future——一个句柄,通过它你可以从一个共享的单对象缓冲区中 get() 一个值,可能需要等待某个 promise 将该值放入缓冲区。
  • promise——一个句柄,通过它你可以将一个值 put() 到一个共享的单对象缓冲区,可能会唤醒某个等待 future 的 thread。
  • packaged_task——一个类,它使得设置一个函数在线程上异步执行变得容易,由 future 来接受 promise 返回的结果。
  • async()——一个函数,可以启动一个任务并在另一个 thread 上执行。

3 改进对泛型编程的支持

3.1 C++11 lambda 表达式

lambda匿名函数的定义

[外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型
{
	函数体;
};
[=](int x, int y) -> bool{ return x < y; } 

#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    int num[4] = {4, 2, 3, 1};
    //对 a 数组中的元素进行排序
    sort(num, num+4, [=](int x, int y) -> bool{ return x < y; } );
    for(int n : num){
        cout << n << " ";
    }
    return 0;
}

lambda外部变量格式

3.2 C++11 变参模板

C++11 引入了变参模板(Variadic Template),它允许函数或类模板接受任意数量的参数。这使得我们能够定义更加灵活的函数和类模板,支持可变数量的参数。在 C++11 中,使用 … 表示变参模板。下面是一个简单示例:

#include <iostream>

// 使用变参模板实现递归打印函数
void print() {
    std::cout << std::endl;
}

template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);
}

int main() {
    print(1, 2, 3, "Hello", 4.5);  // 调用变参模板函数 print

    return 0;
}

3.3 C++11 别名

在 C++11 中,引入了类型别名(Type Alias)功能,允许为已有的类型定义一个新的名称。这种类型别名可以提高代码的可读性、简化复杂类型的书写,并且可以方便地修改类型定义而不需要改变使用该类型的代码。

typedef int myInt;  // 将 int 定义为 myInt 类型的别名
typedef std::vector<int> IntVector;  // 将 std::vector<int> 定义为 IntVector 类型的别名

using myInt = int;  // 将 int 定义为 myInt 类型的别名
using IntVector = std::vector<int>;  // 将 std::vector<int> 定义为 IntVector 类型的别名

using StringList = std::list<std::string>;  // 将 std::list<std::string> 定义为 StringList 类型的别名
using IntMatrix = std::vector<std::vector<int>>;  // 将 std::vector<std::vector<int>> 定义为 IntMatrix 类型的别名

3.4 C++11 tuple

在 C++11 中引入了 std::tuple 类模板,它是一个通用的元组(Tuple)类,用于存储多个不同类型的值。std::tuple 可以看作是一个固定大小的、类型安全的、不可修改的集合。使用 std::tuple 可以方便地组合多个值,而无需定义新的结构体或类。下面是一个简单的示例:

#include <iostream>
#include <tuple>

int main() {
    // 创建一个包含 int、double 和字符串的 tuple
    std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");

    // 使用 std::get 访问 tuple 中的元素(通过索引)
    int intValue = std::get<0>(myTuple);
    double doubleValue = std::get<1>(myTuple);
    std::string stringValue = std::get<2>(myTuple);

    std::cout << "int value: " << intValue << std::endl;
    std::cout << "double value: " << doubleValue << std::endl;
    std::cout << "string value: " << stringValue << std::endl;

    return 0;
}

参考:
C++11新特性(全详解)
C++11、C++14、C++17、C++20新特性总结(5万字详解)

二、C++14

1.函数返回值类型推导

C++14对函数返回类型推导规则做了优化,以下代码在C++11中是不能通过编译

#include <iostream>
using namespace std;
template<typename T> auto func(T t) { return t; }
auto func(int i) {
    return i;
}

int main() {
    cout << func(4) << endl;
    return 0;
}

注意:

  • 函数内如果有多个return语句,它们必须返回相同的类型,否则编译失败。
  • 如果return语句返回初始化列表,返回值类型推导也会失败。
  • 如果函数是虚函数,不能使用返回值类型推导。
  • 返回类型推导可以用在前向声明中,但是在使用它们之前,翻译单元中必须能够得到函数定义。
  • 返回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导,以便编译器推导出返回类型。

2.lambda参数auto

在C++14中,对此进行优化,lambda表达式参数可以直接是auto:

auto f = [] (auto a) { return a; };
cout << f(1) << endl;
cout << f(2.3f) << endl;

3.变量模板

template<class T>
constexpr T pi = T(3.1415926535897932385L);

int main() {
    cout << pi<int> << endl; // 3
    cout << pi<double> << endl; // 3.14159
    return 0;
}

4.别名模板

template<typename T, typename U>
struct A {
    T t;
    U u;
};

template<typename T>
using B = A<T, int>;

int main() {
    B<double> b;
    b.t = 10;
    b.u = 20;
    cout << b.t << endl;
    cout << b.u << endl;
    return 0;
}

5.constexpr的限制

C++14减少了一些限制:

  • C++11中constexpr函数可以使用递归,在C++14中可以使用局部变量和循环
constexpr int factorial(int n) { // C++14 和 C++11均可
    return n <= 1 ? 1 : (n * factorial(n - 1));
}
constexpr int factorial(int n) { // C++11中不可,C++14中可以
    int ret = 0;
    for (int i = 0; i < n; ++i) {
        ret += i;
    }
    return ret;
}
  • C++11中constexpr函数必须必须把所有东西都放在一个单独的return语句中,而constexpr则无此限制:
constexpr int func(bool flag) { // C++14 和 C++11均可
    return 0;
}

constexpr int func(bool flag) { // C++11中不可,C++14中可以
    if (flag) return 1;
    else return 0;
}

6.[[deprecated]]标记

C++14中增加了deprecated标记,修饰类、变、函数等,当程序中使用到了被其修饰的代码时,编译时被产生警告,用户提示开发者该标记修饰的内容将来可能会被丢弃,尽量不要使用。

7.二进制字面量与整形字面量分隔符

C++14引入了二进制字面量,也引入了分隔符,防止看起来眼花

int a = 0b0001'0011'1010;
double b = 3.14'1234'1234'1234;

8.std::make_unique

C++11中有std::make_shared,却没有std::make_unique,在C++14已经改善。

struct A {};
std::unique_ptr<A> ptr = std::make_unique<A>();

9.std::shared_timed_mutex与std::shared_lock

C++14通过std::shared_timed_mutex和std::shared_lock来实现读写锁,保证多个线程可以同时读,但是写线程必须独立运行,写操作不可以同时和读操作一起进行。

struct ThreadSafe {
    mutable std::shared_timed_mutex mutex_;
    int value_;

    ThreadSafe() {
        value_ = 0;
    }

    int get() const {
        std::shared_lock<std::shared_timed_mutex> loc(mutex_);
        return value_;
    }

    void increase() {
        std::unique_lock<std::shared_timed_mutex> lock(mutex_);
        value_ += 1;
    }
};

10.std::integer_sequence

11.std::exchange

12.std::quoted

三、C++17

1.构造函数模板推导

在C++17前构造一个模板类对象需要指明类型:

pair<int, double> p(1, 2.2); // before c++17

C++17就不需要特殊指定,直接可以推导出类型,代码如下:

pair p(1, 2.2); // c++17 自动推导
vector v = {1, 2, 3}; // c++17

2.结构化绑定

通过结构化绑定,对于tuple、map等类型,获取相应值会方便很多。

std::tuple<int, double> func() {
    return std::tuple(1, 2.2);
}

int main() {
    auto[i, d] = func(); //是C++11的tie吗?更高级
    cout << i << endl;
    cout << d << endl;
}

//==========================
void f() {
    map<int, string> m = {
      {0, "a"},
      {1, "b"},  
    };
    for (const auto &[i, s] : m) {
        cout << i << " " << s << endl;
    }
}

// ====================
int main() {
    std::pair a(1, 2.3f);
    auto[i, f] = a;
    cout << i << endl; // 1
    cout << f << endl; // 2.3f
    return 0;
}

3.if-switch语句初始化

C++17前if语句需要这样写代码:

int a = GetValue();
if (a < 101) {
    cout << a;
}

C++17之后可以这样:

// if (init; condition)

if (int a = GetValue()); a < 101) {
    cout << a;
}

string str = "Hi World";
if (auto [pos, size] = pair(str.find("Hi"), str.size()); pos != string::npos) {
    std::cout << pos << " Hello, size is " << size;
}

4.内联变量

C++17前只有内联函数,现在有了内联变量,我们印象中C++类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的:

// header file
struct A {
    static const int value;  
};
inline int const A::value = 10;

// ==========或者========
struct A {
    inline static const int value = 10;
}

5.折叠表达式

C++17引入了折叠表达式使可变参数模板编程更方便:

template <typename ... Ts>
auto sum(Ts ... ts) {
    return (ts + ...);
}
int a {sum(1, 2, 3, 4, 5)}; // 15
std::string a{"hello "};
std::string b{"world"};
cout << sum(a, b) << endl; // hello world

6.constexpr lambda表达式

C++17前lambda表达式只能在运行时使用,C++17引入了constexpr lambda表达式,可以用于在编译期进行计算。

int main() { // c++17可编译
    constexpr auto lamb = [] (int n) { return n * n; };
    static_assert(lamb(3) == 9, "a");
}

7.namespace嵌套

namespace A {
    namespace B {
        namespace C {
            void func();
        }
    }
}

// c++17,更方便更舒适
namespace A::B::C {
    void func();)
}

8.__has_include预处理表达式

可以判断是否有某个头文件,代码可能会在不同编译器下工作,不同编译器的可用头文件有可能不同,所以可以使用此来判断:

9.在lambda表达式用*this捕获对象副本

正常情况下,lambda表达式中访问类的对象成员变量需要捕获this,但是这里捕获的是this指针,指向的是对象的引用,正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构了,还访问了成员变量,就会有问题。所以C++17增加了新特性,捕获*this,不持有this指针,而是持有对象的拷贝,这样生命周期就与对象的生命周期不相关啦。

10.新增Attribute

  • [[fallthrough]],用在switch中提示可以直接落下去,不需要break,让编译期忽略警告
  • [[nodiscard]] :表示修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理
  • [[maybe_unused]] :提示编译器修饰的内容可能暂时没有使用,避免产生警告

11.字符串转换

新增from_chars函数和to_chars函数

#include <charconv>

int main() {
    const std::string str{"123456098"};
    int value = 0;
    const auto res = std::from_chars(str.data(), str.data() + 4, value);
    if (res.ec == std::errc()) {
        cout << value << ", distance " << res.ptr - str.data() << endl;
    } else if (res.ec == std::errc::invalid_argument) {
        cout << "invalid" << endl;
    }
    str = std::string("12.34);
    double val = 0;
    const auto format = std::chars_format::general;
    res = std::from_chars(str.data(), str.data() + str.size(), value, format);
    
    str = std::string("xxxxxxxx");
    const int v = 1234;
    res = std::to_chars(str.data(), str.data() + str.size(), v);
    cout << str << ", filled " << res.ptr - str.data() << " characters \n";
    // 1234xxxx, filled 4 characters
}

12.std::variant

C++17增加std::variant实现类似union的功能,但却比union更高级,举个例子union里面不能有string这种类型,但std::variant却可以,还可以支持更多复杂类型,如map等,看代码:

int main() { // c++17可编译
    std::variant<int, std::string> var("hello");
    cout << var.index() << endl;
    var = 123;
    cout << var.index() << endl;

    try {
        var = "world";
        std::string str = std::get<std::string>(var); // 通过类型获取值
        var = 3;
        int i = std::get<0>(var); // 通过index获取对应值
        cout << str << endl;
        cout << i << endl;
    } catch(...) {
        // xxx;
    }
    return 0;
}

13.std::optional

我们有时候可能会有需求,让函数返回一个对象。有一种办法是返回对象指针,异常情况下就可以返回nullptr啦,但是这就涉及到了内存管理,也许你会使用智能指针,但这里其实有更方便的办法就是std::optional。

std::optional<int> StoI(const std::string &s) {
    try {
        return std::stoi(s);
    } catch(...) {
        return std::nullopt;
    }
}

void func() {
    std::string s{"123"};
    std::optional<int> o = StoI(s);
    if (o) {
        cout << *o << endl;
    } else {
        cout << "error" << endl;
    }
}

14.std::any

C++17引入了any可以存储任何类型的单个值

15.std::apply

使用std::apply可以将tuple展开作为函数的参数传入

16.std::make_from_tuple

使用make_from_tuple可以将tuple展开作为构造函数参数

struct Foo {
    Foo(int first, float second, int third) {
        std::cout << first << ", " << second << ", " << third << "\n";
    }
};
int main() {
   auto tuple = std::make_tuple(42, 3.14f, 0);
   std::make_from_tuple<Foo>(std::move(tuple));
}

17.as_const

C++17使用as_const可以将左值转成const类型

std::string str = "str";
const std::string& constStr = std::as_const(str);

18.std::string_view

通常我们传递一个string时会触发对象的拷贝操作,大字符串的拷贝赋值操作会触发堆内存分配,很影响运行效率,有了string_view就可以避免拷贝操作,平时传递过程中传递string_view即可。

19.file_system

C++17正式将file_system纳入标准中,提供了关于文件的大多数功能,基本上应有尽有。

20.std::shared_mutex

C++17引入了shared_mutex,可以实现读写锁

四、C++20

C++20新特性概览

1.模块(Modules)

优点:

  • 代替头文件
  • 声明实现仍然可分离, 但非必要
  • 两模块内可以拥有相同名字
  • 预处理宏只在模块内有效
  • 模块只处理一次
  • 不需要防卫式声明
  • 模块引入顺序无关紧要
  • 所有头文件都是可导入的
    modules的出现彻底改变了我们组织代码文件的形式,我们不需要分为.h和.cpp文件并保证编译的一次性(pragma once或防卫式头)。
// module.cpp 创建模块
import <iostream>
export module MyModule; 
export void MyFunc() 
{
    std::cout << "This is my function" << std::endl;
}
// main.cpp 引用模块
import MyModule;
int main(int argc, char** argv) 
{ 
    MyFunc();
}

2.范围库(Ranges)

Ranges 是什么 ?

  • Range 代表一组元素, 或者一组元素中的一段
  • 类似 begin/end 这样的迭代器对

3.概念库(Concepts)

作用:

  • 对模板类、模板函数的模板参数进行约束
  • 编译期进行约束
  • 帮助编译错误信息

4.协程(Coroutines)

协程概念:

一个可以记住自身状态,可随时挂起和执行的函数。

相关关键字:

  • co_wait: 挂起协程, 等待其它计算完成
  • co_return: 从协程中返回
  • co_yield: 弹出一个值, 挂起协程, 下一次调用继续协程的运行

用处:

  • 生成器
  • 异步I/O
  • 延迟计算
  • 事件驱动的程序

5.并发库(Concurrency)

如何将智能指针变成线程安全?

  • 使用 mutex 控制智能指针的访问
  • 使用全局非成员原子操作函数访问, 诸如: std::atomic_load(), atomic_store(), …
  • C++20提供了支持, atomic

C++20增加了可以自动join,可以随时中断的线程jthread。选择增加新类型jthread,而不是更改thread,体现了开放封闭原则。

  • std::jthread : 支持中断; 析构函数中自动 join; 可以中断线程执行stop_token
  • 中断机制
  • std::stop_token : 中断的实际请求者; 用来查询线程是否中断; 可以和condition_variable_any配合使用
  • std::stop_source : 中断源; 用来请求线程停止运行; stop_resources 和 stop_tokens 都可以查询到停止请求
  • std::stop_callback : 中断的回调函数; 如果对应的stop_token 被要求终止, 将会触发回调函数

6.同步库(Synchronization)

同步库主要用于线程的同步,我们知道线程同步有两种方式经典方式:

  • 轮询
  • 通知

C++11只提供了几种方式,C++20大大增强了同步的各种方式。

  • 信号量(Semaphore)
  • 锁存器(Latches)
  • 屏障(Barriers)
  • std::atomic 等待和通知接口
  • std::atomic_ref

7.Lambda 表达式的更新

  • [=, this] 需要显式捕获this变量
  • 模板形式的 Lambda 表达式
  • 3.支持初始化捕捉

8.指定初始化(Designated Initializers)

在很多时候,我们可能由于成员过多,不记得构造函数的元素循序,进行构造是必须再次查看对应关系才能进行初始化。现在你只要知道你想初始化的条目即可完成正确的构造。

struct Data { 
    int anInt = 0; 
    std::string aString; 
}; 
Data d{ .aString = "Hello" };

9.船型操作符 <=> (三路比较运算符)

- (a <=> b) < 0 // 如果 a < b 则为 true
- (a <=> b) > 0 // 如果 a > b 则为 true
- (a <=> b) == 0 // 如果 a 与 b 相等或者等价 则为 true

10.范围 for 循环语句支持初始化语句

C++17开始支持switch 语句初始化和if 语句初始化,现在C++20发扬风范,开始支持范围 for 循环初始化 ,其样例代码如下:

struct Foo { 
    std::vector<int> values; 
}; 

Foo GetData() { 
    return Foo(); 
} 

int main() { 
    for (auto data = GetData(); auto& value : data.values) { 
        // Use 'data’ 
    } 
}

11.非类型模板形参支持字符串

12.C++属性符

  • [[likely]], [[unlikely]] 先验概率指导编译器优化
  • [[nodiscard(reason)]] 表明返回值不可抛弃, 加入理由的支持

13.日历(Calendar)功能

  • 增加日历和时区的支持
  • 只支持公历(Gregorian calendar)
  • 其他日历也可通过扩展加入, 并能和 进行交互

14.时区(Timezone)功能

15.std::span

16.特性测试宏

通过它可以判断编译器是否支持某个功能

17.常量表达式(constexpr) 的更新

constexpr的意义:

constexpr(常量表达式)是为了解决C++历史遗留问题,它一种比const 更严格的束缚, 它规定了表达式本身在编译期间可知。具体来说有以下特性:

  • const是运行期常量 constexpr是编译期常量
  • const其实是readonly(只读),而constexpr才是const(常量)
  • constexpr 所修饰的函数,返回值则不一定要求是编译期常量 ==>函数返回值一定是编译时常量,且在编译期进行计算(C++20 consteval)
    C++20constexpr

18.consteval 函数

constexpr 函数可能编译期执行, 也可以在运行期执行。 consteval 只能在编译器执行, 如果不满足要求编译不通过。

19.constinit

在这里插入图片描述

20.用 using 引用 enum类型

为了提高枚举类型的安全性和数据类型指定性(继承内置整数类型),防止命名冲突,进而引入了emun class来对枚举进行作用域限制。但是代码中必须标明类型名,这样很不方便。因此,C++20使用了与命名空间,类内类型一致解决方法:using。但是为了减少暴露、防止入侵式代码,建议在尽可能小的作用域打开枚举。

enum class CardTypeSuit { 
    Clubs, 
    Diamonds, 
    Hearts, 
    Spades 
}; 

std::string_view GetString(const CardTypeSuit cardTypeSuit) { 
    switch (cardTypeSuit) { mingmin222
    case CardTypeSuit::Clubs: 
        return "Clubs"; 
    case CardTypeSuit::Diamonds: 
        return "Diamonds"; 
    case CardTypeSuit::Hearts: 
         return "Hearts"; 
    case CardTypeSuit::Spades: 
         return "Spades";henbufangbian
    } 
} 
std::string_view GetString(const CardTypeSuit cardTypeSuit) { 
    switch (cardTypeSuit) { 
        using enum CardTypeSuit; // 这里是关键
        case Clubs: return "Clubs"; 
        case Diamonds: return "Diamonds"; 
        case Hearts: return "Hearts"; 
        case Spades: return "Spades"; 
    } 
}

21.格式化库(std::format)

这是一个十分类似于Python 的字符串格式化

std::string s = std::format("Hello, world!\n");       // s == "Hello, world!\n"
std::string s = std::format("The answer is {}.", 42); // s == "The answer is 42."
std::string s = std::format("I'd rather be {1} than {0}.", "right", "happy");    // s == "I'd rather be happy than right."
std::vector<int> v = {1, 2, 3}; std::string s = fmt::print("{}\n", v);           // s == "[1, 2, 3]"
// 当然还支持各种格式化输出如:时间,日期, 年月
// 还支持各种输出流指定比如文件,控制台,数据库

22.增加数学常量

  • 头文件
  • 包含 e, log2e, log10e pi, inv_pi, inv_sqrt pi ln2, ln10, sqrt2, sqrt3, inv_sqrt3, egamma
  • 他们的精度很高,我们再也不需要自己去查找定义这些数值了

23.std::source_location

主要是用于来获取代码位置, 对于日志和错误信息尤其有用!

24.位运算

加入循环移位, 计数0和1位等功能

总结

提示:自用
学习一下C++11、C++14、C++17、C++20新特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值