语言可用性的强化
常量
-
nullptr
传统c++会把NULL、0解析成一种东西,有些编译器会把NULL定义成((void*)0)或者0。使用NULL调用函数会导致c++重载特性混乱。比如:
void foo(char*),
void foo(int)
foo(NULL)会调用foo(int).
C++11 引入了 nullptr 关键字,专门用来区分空指针、0。而 nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。 -
constexpr
C++ 标准中数组的长度必须是一个常量表达式。constexpr关键字可以修饰函数和变量,作用是把编译时就把这些表达式直接优化并植入到程序运行时。
变量及初始化
- if/switch 变量声明强化
c++17后能够在if/switch语句中定义局部变量。比如:if(int i=0;i>=0){…}。 - 初始化列表
c++11加入std::initializer_list。初始化列表可以使用在类构造函数和普通函数上。
void foo(std::initializer_list list) {…}
Foo foo2 {3, 4}; - 结构化绑定
c++11加入。
#include <iostream>
#include <tuple>
std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.3, "456");
}
int main() {
auto [x, y, z] = f();
std::cout << x << ", " << y << ", " << z << std::endl;
return 0;
}
类型推导
- auto
auto不能用于函数传参,不能推导数组类型。 - decltype
计算某个表达式的类型。 - 尾返回类型推导
C++11:尾返回类型(trailing return type)
template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}
C++14: 直接返回类型推导
template<typename T, typename U>
auto add3(T x, U y){
return x + y;
}
- decltype(auto)
对转发函数或封装的返回类型进行推导。
std::string foo(){...}
decltpye(auto) foo1(){
return foo();
}
- if constexpr
C++17 将 constexpr 这个关键字引入到 if 语句中,允许在代码中声明常量表达式的判断条件。 - 区间for迭代
C++11加入:for(auto item:arr){…}
模板
- 外部模板
C++11加入。显式的通知编译器何时进行模板的实例化。extern template class std::vector<double>; - 尖括号">"
C++11加入。vector<vector<int>> a; - 类型别名模板
typedef无法为模板定义一个别名,因此C++11加入using,支持对模板定义别名。using TrueDarkMagic = MagicType<std::vector, std::string>; - 变长参数模板
C++11 加入了新的表示方法, 允许任意个数、任意类别的模板参数,同时也不需要在定义时将参数的个数固定:template<typename… Ts> class Magic;。个数为0的模板参数也可以。使用sizeof…()计算参数个数。
变参模板展开
C++17加入,作用是展开变长模板参数。
初始化列表展开 - 折叠表达式
C++17加入。void foo(int …t); - 非类型模板参数推导
C++11加入:template <typename T, int BufSize>
C++17加入:template <auto value> void foo();
面对对象
- 委托构造
C++11加入,使得在同一个类中的构造函数可以调用另一个构造函数。 - 继承构造
C++11加入,使用using继承构造函数。 - 显式虚函数重载
C++11加入override 和 final,防止意外重载虚函数。override告诉编译器这是一个重载函数,编译器将检查基函数是否存在这样的虚函数。final防止类继续被继承,终止虚函数继续重载。 - 显式禁用默认函数
C++11加入=default和=delete。 - 强类型枚举
C++11 引入了枚举类(enumeration class),并使用 enum class 的语法进行声明。
语言运行期的强化
Lambda表达式
-
基础
基本语法:[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 { // 函数体 }
值捕获:[value]{…}。捕获并复制函数上层上下文的参数,类似于函数传值。
引用捕获:[&value]{…}。与值捕获类似,不同的是捕获的是引用。
隐式捕获:[=]值捕获,[&]引用捕获,让编译器推导捕获列表。
表达式捕获:C++14加入,允许右值捕获。被声明的捕获变量会根据表达式判断,判断方式与auto相同。[v1=1](int x,int y){return v1+x+y};_ -
泛型Lambda
auto不能用在函数参数上,因为这会与模板功能产生冲突。C++14开始,Lambda函数的形参可以是auto。
函数对象包装器
- std::function
是一种通用、多态的函数封装。 - std::bind和std::placeholder
std::bind 则是用来绑定函数调用的参数的。std::placeholder用来对参数占位、
右值引用
左值(lvalue, left value),顾名思义就是赋值符号左边的值。准确来说, 左值是表达式(不一定是赋值表达式)后依然存在的持久对象。
右值(rvalue, right value),右边的值,是指表达式结束后就不再存在的临时对象。
纯右值(prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10, true; 要么是求值结果相当于字面量或匿名临时对象,例如 1+2。非引用返回的临时变量、运算表达式产生的临时变量、 原始字面量、Lambda 表达式都属于纯右值。
将亡值(xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中, 纯右值和右值是同一个概念),也就是即将被销毁、却能够被移动的值。
移动语义
默认的移动操作,必须先复制再析构,造成了大量浪费资源的操作。
完美转发
无论模板参数是什么类型的引用,当且仅当实参类型为右引用时,模板参数才能被推导为右引用类型。std::forward 和 std::move 一样,没有做任何事情,std::move 单纯的将左值转化为右值, std::forward 也只是单纯的将参数做了一个类型的转换,从现象上来看, std::forward(v) 和 static_cast<T&&>(v) 是完全一样的。
无序容器
C++11 引入了两组无序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。
元组
-
基本操作
关于元组的使用有三个核心的函数:
std::make_tuple: 构造元组
std::get: 获得元组某个位置的值
std::tie: 元组拆包 -
运行时索引
使用 std::variant<>(C++ 17 引入),提供给 variant<> 的类型模板参数 可以让一个 variant<> 从而容纳提供的几种类型的变量(在其他语言,例如 Python/JavaScript 等,表现为动态类型) -
元组合并与遍历
合并两个元组,这可以通过 std::tuple_cat 来实现。
智能指针与内存管理
-
RAII与引用计数
C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。 -
std::shared_ptr
std::shared_ptr 是一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象,从而消除显式的调用 delete,当引用计数变为零的时候就会将对象自动删除。
std::make_shared 就能够用来消除显式的使用 new,所以std::make_shared 会分配创建传入参数中的对象, 并返回这个对象类型的std::shared_ptr指针。 -
std::unique_ptr
std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全。 -
std::weak_ptr
std::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。弱引用不会引起引用计数增加。