回顾前面介绍过的 C++ 11 功能
long long
、unsigned long long
char16_t
、char32_t
原始字符串 R"..."
初始化列表
std::initializer_list
auto
、decltype
、返回类型后置、模板别名(using =
)
智能指针
异常规范、noexcept
explicit
类内成员初始化(在类定义中初始化成员),但只能使用等号或大括号版本的初始化
基于范围的 for
循环
右值引用
移动语义(Move Semantics)和右值引用
在原来的 C++ 中,只有拷贝构造函数,对于下面的代码:
vector<string> allcaps(const vector<string>& vs) {
vector<string> temp;
...
return temp;
}
vector<string> vstr;
vector<string> vstr_copy1(vstr); // #1
vector<string> vstr_copy2(allcaps(vstr)); // #2
在上面的代码的 #2 中,allcaps(vstr)
创建了对象 temp
临时对象被返回,交给了 vector<string>
的拷贝构造函数,多了一次拷贝构造
显然,直接将 allcaps(vstr)
的结果所有权转让给 vstr_copy2
更好
这种方法被称为移动语义
移动语义的一个实践是构造函数,移动构造函数的定义像下面这样:
class Useless {
private:
int n;
char* pc;
public:
...
Useless::Useless(Useless&& f);
};
Useless::Useless(Useless&& f) : n(f.n) {
pc = f.pc;
f.pc = nullptr;
f.n = 0;
}
移动构造函数窃取(pilfering)了所有权,并将原来的对象清空
使用左值时,将调用拷贝构造函数,使用右值时调用移动构造函数:
Useless two = one; // 拷贝
Useless four(one + three); // 移动
移动语义也适用于赋值运算符:
class Useless {
private:
int n;
char* pc;
public:
...
Useless& operator=(Useless&& f);
};
Useless& Useless::operator=(Useless&& f) {
if (this == &f) {
return *this;
}
delete [] pc;
n = f.n;
pc = f.pc;
f.pc = nullpr;
f.n = 0;
return *this;
}
移动构造函数和移动赋值使用右值,如果我们想要使用左值,需要强制移动:
Useless one;
Useless two = std::move(one);
此时,如果 Useless
定义了移动赋值,那么就会强制调用移动赋值运算符
新的类功能
特殊的成员函数
在原有的 4 个特殊成员函数(默认构造函数、拷贝构造函数、拷贝赋值运算符、析构函数)的基础上,C++ 11 新增了两个:移动构造函数和移动赋值运算符。这些成员函数时编译器在各种情况下自动提供的
默认的移动构造函数和移动赋值运算符与拷贝版本相似:逐成员初始化并移动,如果成员没有移动构造或赋值运算符,那么执行拷贝
注意默认的移动构造函数和移动赋值运算符遇到指针成员时,不会将源指针设置为 nullptr
,因此可能导致悬垂指针的问题
默认方法和禁用方法
可以使用 delete
禁止编译器使用特定方法,例如:
class Some {
public:
void redo(double);
void redo(int) = delete;
};
int main() {
Some s;
s.redo(5);
}
上面的代码会报错,因为 redo(int)
被标注为 delete
可以显示标注 default
来让编译器提供默认的成员函数(只适用于 6 个特殊成员函数)
委托构造函数
C++ 11 允许在一个构造函数的定义中使用另一个构造函数,如:
class Notes {
int k;
double x;
public:
Notes();
Notes(int);
Notes(int, double);
};
Notes::Notes() : Notes(1, 0.1) {}
Notes::Notes(int k_) : Notes(k_, 0.1) {}
Notes::Notes(int k_, double x_) : k(k_), x(x_) {}
这种情况叫做委托构造
继承构造函数
C++ 11 提供了一种让派生类能够继承基类构造函数的机制,如:
class BS {
public:
BS();
BS(int k);
BS(double x);
BS(int k, double x);
};
class DR : public BS {
public:
using BS::BS;
DR();
DR(int k);
DR(double x);
};
int main() {
DR dr1;
DR dr2(1);
DR dr3(0.1);
DR dr4(1, 0.1);
}
上面的代码中,DR
继承了 BS
的所有构造函数,但是其中三个被覆盖了,所以只有 dr4
使用了 BS
的构造函数
管理虚方法:override
和 final
在虚方法中,如果子类的某个成员函数与父类同名,但是函数签名不同,那么并不会覆盖父类的版本,而是会隐藏父类的版本,如:
class Action {
public:
virtual void f(char ch) const;
};
class Bingo {
public:
virtual void f(char* ch) const;
};
int main() {
Bingo b(10);
b.f('@');
}
上面的代码将报错
在 C++ 11 中可以使用虚说明符 override
表明要覆盖一个虚函数,如:
virtual void f(char* ch) const override;
现在,编译器在创建类时就会报错
如果不想要子类覆盖特定的虚方法,可以使用 final
,如:
virtual void f(char* ch) const final;
注意,override
和 final
并非关键字,而是具有特殊意义的标识符,在正常情况下是可以用它做变量名的
Lambda 函数
lambda 这个名称来自 lambda calculus(lambda 演算)
可以把 lambda 函数看成是一个函数对象,它可以捕获环境
包装器
C++ 提供了多个包装器(wrapper,也可以叫适配器 adapter)
这些对象用于给其他编程接口提供更一致或更合适的接口,例如前面的 bin1st
和 bind2nd
C++ 11 提供了其他的包装器,包括:
- 模板
bind
:可以代替bin1st
和bind2nd
,更灵活 - 模板
men_fn
:能够将成员函数作为常规函数进行传递 - 模板
reference_wrapper
:能够创建行为像引用但可以被复制的对象 - 包装器
function
:能够以统一的方式处理多种类似于函数的形式
包装器 function
可调用类型(callable type)很多,可能导致模板的效率极低,如:
template <typename T, typename F>
T use_f(T v, F f) { ... }
class Fp {
public:
double operator()(double q) { ... }
}; // 函数对象
class Fq {
public:
double operator()(double q) { ... }
}; // 函数对象
double dub(double x) { ... }
double square(double x) { ... }
int main() {
double x = 1.1;
use_f(x, dub);
use_f(x, square);
use_f(x, Fp());
use_f(x, Fq());
use_f(x, [](double u) { return u * u; });
use_f(x, [](double u) { return u + u / 2.0; });
}
上面的代码将会实例化 5 个 use_f
模板(dub
和 squre
类型相同)
function
包装器可以解决这个问题,上面的代码可以改成:
std::function<double(double)> ef1 = dub;
std::function<double(double)> ef2 = square;
std::function<double(double)> ef3 = Fp();
std::function<double(double)> ef4 = Fq();
std::function<double(double)> ef5 = [](double u) { return u * u; };
std::function<double(double)> ef6 = [](double u) { return u + u / 2.0; };
这样的话就只实例化了一次,function
包装器的类型参数是 return_type(arg1_type, arg2_type, ...)
也可以更改函数模板:
template <typename T, typename F>
T use_f(T v, std::function<T(T)> f) { ... }
可变参数模板(variadic template)
可变参数模板让我们可以创建这样的模板函数和模板类:可以接受可变数量的参数
要理解这几个要点:
- 模板参数包(parameter pack)
- 函数参数包
- 展开(unpack)参数包
- 递归
模板和函数参数包
C++ 11 提供了一个用省略号表示的元运算符(meta-operator)
template<typename... Args>
void show_list(Args... args) {
...
}
其中,Args
是一个模板参数包,args
是一个函数参数包
这个函数可以与 0 或多个参数的函数调用匹配
递归与展开参数包
我们可以通过递归来展开参数包,如:
template<typename T, typename... Args>
void show_list_(T value, Args... args) {
// 对 value 进行操作
show_list_(args...); // 将剩下的东西继续传递
}
// 定义 0 个参数的函数,用于终止递归
void show_list() {
...
}
可以指定展开模式(pattern),例如改成引用传递:
template<typename T, typename... Args>
void show_list_(T value, const Args&... args) { ... }
这样会对每个函数参数应用模式 const Type&
C++ 11 新增的其他功能
线程相关:
thread_local
:持续性与线程相关的静态存储- 原子操作库提供了头文件 atomic
- 线程支持库提供了头文件 thread、mutex、condition_variable 和 future
新增的专用库:
- random 头文件:提供了大量比
rand()
复杂的随机数工具 - chrono 头文件:提供了处理时间间隔的途径
- tuple 头文件:元组
- ratio 头文件:编译阶段有理数算术库
- regex 头文件:正则表达式
低级编程:
- 放松了 POD(Plain Old Data)的要求
- 允许结构体有构造函数和析构函数,但是不能有虚函数
alignof
、alignas
可以查看和更改对齐方式constexpr
编译期计算表达式
其他杂项:
- 字面量运算符(literal operator):
ReturnType operator"" _n(...)
- 调试工具:
assert
宏、static_assert
宏 - 加强了对元编程(metaprogramming)的支持