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
.