C++11/14/17新特性拾遗
⓫变长参数模板(Variadic Template)
...
称为元运算符(meta operator),在不同位置出现有不同的释义,如以下打印变参个数的代码。
1 | template<typename... T> |
参数解包有以下几种方式。
1 | void print() { cout << '\n'; } |
1 | template<typename T, typename... Ts> |
1 | template<typename T, typename... Ts> |
⓫模板元编程(TMP; Template Meta Programming)
基础
元程序即meta program,意味着a program about a program。
Enum Hack
使用枚举常量代替整型常量,模板元编程的一项基本技术。
1 | template<int N> struct fib { |
特性萃取技术
需包含如下头文件。
1 |
在编译期确定某种类型是否具有某种特性。
struct
属于class
,is_class
用于检测是否为struct
或class
。
函数指针不被
is_function
检测。
1 | using INT = int; |
确定数组之维度,或某一维之长度。
若出现错误,则
value
为0。
1 | cout << std::rank<int[10]>::value << '\n' << // 1 |
SFINAE
SFINAE(Substitution Failure is not an Error)
匹配失败不是错误。
1 | struct Test { using Foo = int; }; |
⓫其他模板强化
模板别名
1 | template<typename T> |
模板默认参数
1 | template<typename T = int> ...; |
外部模板
1 | extern template class A<TYPE>; // 不在该当前编译文件中实例化 |
⓫右值引用
右值引用使临时值的生命周期延长至变量存货周期。
左值:能被取地址的都是左值;
因此,右值引用变量、字符串字面量等都是左值。
右值:(字符串字面量以外的)字面量和临时对象。
将亡值(XValue; Expiring Value)
与右值引用相关,既有泛左值,也有右值。
引用类型及其可以引用的值类型如下表。
引用类型 | 非常左值 | 常左值 | 非常右值 | 常右值 | 注记 |
---|---|---|---|---|---|
非常量左值引用 | ✓ | 无 | |||
常量左值引用 | ✓ | ✓ | ✓ | ✓ | 全能类型,可用于拷贝语义 |
非常量右值引用 | ✓ | 用于移动语义 | |||
常量右值引用 | ✓ | ✓ | 暂无用途 |
1 | const int && p = 5; |
⓫auto
和decltype
的类型推断
auto
总是推断出值类型,auto&&
则总是引用类型;decltype(e)
得到表达式值类型,decltype((e))
得到引用类型。
auto
推断丢失引用信息,decltype
推断保留引用信息。
1 | int x = 0; |
尾返回类型(Trailing Return Type)推导
1 | template<typename T, typename U> |
⓮普通函数具备返回值推导能力。
1 | template<typename T, typename U> |
⓮泛型lambda表达式
使用
auto
而非template
。
1 | auto f = [](auto x) { ... }; |
⓮decltype(auto)
主要用于对转发函数或封装的返回类型进行推导。
1 | auto f = []() -> int&& {}; |
此处x3
的声明相当于如下代码。
1 | decltype(auto) x3 = f(); // x3: int&& |
⓫移动语义
1 | class Moveable { |
使用移动语义的emplace_back()
使高效率。
1 | vector<complex<double>> v; |
引用折叠/坍缩(Reference Collapsing)
又称左值引用传染,即如下的规则。
T&
+&
=T&
T&
+&&
=T&
T&&
+&
=T&
T&&
+&&
=T&&
完美转发(Perfect Forwarding)
在函数模板中,完全依照模板参数之类型,将参数传递给函数模板中调用的另一函数,称完美转发。
1 | void check(int &) { cout << "lvalue\n"; } |
⓫继承构造函数
1 | class Derived: public Base { |
⓫线程(Thread)
Linux下需要向编译器指明
-pthread
。
基础
join()
:等待线程结束;detach()
:分离执行,互不影响。
Once a thread detached, we cannot force it to join with the main thread again.
1 | t.detach(); |
可使用joinable()
检查线程。
1 | if(t.joinable()) t.join(); |
新建线程,单纯传递仿函数时需要额外的一对括号。
1 | std::thread t((Functor())); |
传递引用参数时,不能仅以&
标记。
1 | std::thread t(&thread_function, std::ref(s)); |
Copying a thread won't compile, but we can transfer the ownership by moving.
1 | std::thread t2 = std::move(t1); |
通过std::this_thread::get_id()
获取当前线程id。
通过std::thread::hardware_concurrency()
获取线程总数。
互斥量与临界区
RAII(Resource Acquisition is Initialization)
资源获取即初始化,下例中的std::lock_guard
体现了这点。
1 | void critical_section(int value) { |
std::lock_guard
不能显式调用lock()
和unlock()
,而std::unique_lock
可以。
If we have an exception after lock() & before unlock(), then we'll have a problem.
1 | // 不推荐! |
借此可实现经典的「生产者—消费者」场景。
下例
wait()
中传入lck
的原因是,条件变量需要先解锁互斥量,然后阻塞,唤醒后再锁上。
1 | mutex mtx; |
1 | void producer(...) { |
1 | void consumer() { |
期物和诺物(std::future
&std::promise
)
下例中f.get()
阻塞直至结果可用,等效地调用f.wait()
等待结果。
1 | void get(const std::future<int> & f) { int res = f.get(); ... } |
future
可来自packaged_task
。
packaged_task
用于封装任何可调用的目标。
1 | std::packaged_task<int()> task([](){ return ...; }); |
future
可来自async()
。
async()
的第一个参数,std::launch::async
指示异步求值,std::launch::deferred
指示惰性求值,默认std::launch::async|std::launch::deferred
,即取决于实现。
1 | std::future<int> f = std::async(std::launch::async, [](){ |
⓫自定义字面值
1 | long operator"" _kb(long v) { return v * 1024; } |
⓱变量声明强化
if
/switch
结构可将临时变量写入括号内。
1 | if(const auto it = find(v.begin(), v.end(), x); it != v.end()) { ... } |
⓱结构化绑定
下例中,
f()
返回一个std::tuple
。
1 | auto [x, y, z] = f(); |
⓫以往使用std::tie
对元组进行拆包。
1 | tie(x, y, z) = f(); |
⓱非类型模板参数推导
1 | template<auto value> void f() { ... } |
⓱折叠表达式(Fold Expression)
1 | template<typename... T> |
左折叠:(...+t)
,其展开形如1+(2+(3+(4+5)))
;
右折叠:(t+...)
,其展开形如(((1+2)+3)+4)+5
。
空包
参数包参数数目为0时,如sum();
,会导致编译错误。
解决空包出错可以将sum()
修正为如下例的二元折叠。
1 | template<typename... T> |
其他
使用0B
书写二进制表示。
1 | int a = 0B101010101010; |
使用'
对数字分组。
1 | int b = 564'190'000; |
新标准弃用的特性
字符串字面量需要const char*
指向;
1 | char* str = "..."; // Deprecated! |
register
不再具备任何含义;
auto_ptr
被弃用,应使用unique_ptr
。