C++11/14/17新特性拾遗

⓫变长参数模板(Variadic Template)

...称为元运算符(meta operator),在不同位置出现有不同的释义,如以下打印变参个数的代码。

1
2
3
4
template<typename... T>
void func(T... args) {
cout << sizeof...(args) << '\n';
}

参数解包有以下几种方式。

1
2
3
4
5
6
7
void print() { cout << '\n'; }

template<typename T, typename... Ts>
void print(T value, Ts... args) {
cout << value << ' ';
print(args...);
}
1
2
3
4
5
6
template<typename T, typename... Ts>
void print(T value, Ts... args) {
cout << value << ' ';
if constexpr(sizeof...(args) > 0) print(args...);
else cout << '\n';
}
1
2
3
4
5
6
7
8
template<typename T, typename... Ts>
void print(T value, Ts... args) {
cout << value << ' ';
initializer_list<T> {(
[&args]{cout << args << ' '; }(),
value)...};
cout << '\n';
}

⓫模板元编程(TMP; Template Meta Programming)

基础

元程序即meta program,意味着a program about a program。

Enum Hack

使用枚举常量代替整型常量,模板元编程的一项基本技术。

1
2
3
4
5
template<int N> struct fib {
enum { result = fib<N-1>::result + fib<N-2>::result };
};
template<> struct fib<1> { enum { result = 1 }; };
template<> struct fib<2> { enum { result = 1 }; };

特性萃取技术

需包含如下头文件。

1
#include <type_traits>

在编译期确定某种类型是否具有某种特性。

struct属于classis_class用于检测是否为structclass

函数指针不被is_function检测。

1
2
3
using INT = int;
cout << std::is_const<int>::value << '\n' << // 0
std::is_same<INT, int>::value << '\n'; // 1

确定数组之维度,或某一维之长度。

若出现错误,则value为0。

1
2
cout << std::rank<int[10]>::value << '\n' << // 1
std::extent<int[2][3], 0>::value << '\n'; // 2

SFINAE

SFINAE(Substitution Failure is not an Error)

匹配失败不是错误。

1
2
3
4
5
6
7
8
9
struct Test { using Foo = int; };
template<typename T> void f(typename T::Foo) { }
template<typename T> void f(T) { }

int main(void) {
f<Test>(0); // Call #1
f<int>(0); // Call #2,没有int::Foo,但这不是错误
return 0;
}

⓫其他模板强化

模板别名

1
2
template<typename T>
using List = Node<T>*; // 无法使用typedef实现

模板默认参数

1
template<typename T = int> ...;

外部模板

1
extern template class A<TYPE>; // 不在该当前编译文件中实例化

⓫右值引用

右值引用使临时值的生命周期延长至变量存货周期。

  • 左值:能被取地址的都是左值;

    因此,右值引用变量、字符串字面量等都是左值。

  • 右值:(字符串字面量以外的)字面量和临时对象。

将亡值(XValue; Expiring Value)

与右值引用相关,既有泛左值,也有右值。

引用类型及其可以引用的值类型如下表。

引用类型 非常左值 常左值 非常右值 常右值 注记
非常量左值引用
常量左值引用 全能类型,可用于拷贝语义
非常量右值引用 用于移动语义
常量右值引用 暂无用途
1
2
const int && p = 5;
int && q = p; // ERROR!

autodecltype的类型推断

  • auto总是推断出值类型,auto&&则总是引用类型;

  • decltype(e)得到表达式值类型,decltype((e))得到引用类型。

auto推断丢失引用信息,decltype推断保留引用信息。

1
2
3
4
5
int x = 0;
auto && x1 = x; // x1: int&
auto & x2 = x; // x2: int&
auto && x3 = std::move(x); // x3: int&&
decltype(x2) x22 = x; // x22: int&

尾返回类型(Trailing Return Type)推导

1
2
template<typename T, typename U>
auto add(T x, U y) -> decltype(x + y) { return x + y; }

⓮普通函数具备返回值推导能力。

1
2
template<typename T, typename U>
auto add(T x, U y) { return x + y; }

⓮泛型lambda表达式

使用auto而非template

1
auto f = [](auto x) { ... };

decltype(auto)

主要用于对转发函数或封装的返回类型进行推导。

1
2
3
4
auto f = []() -> int&& {};
auto x1 = f(); // x1: int
auto && x2 = f(); // x2:int&&
decltype(f()) x3 = f(); // x3: int&&

此处x3的声明相当于如下代码。

1
decltype(auto) x3 = f(); // x3: int&&

⓫移动语义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Moveable {
private:
int x;
public:
Moveable(int a): x(a) {}
Moveable(Moveable && other) noexcept {
std::swap(x, other.x);
}
//
Moveable & operator=(Mobeable && other) noexcept {
std::swap(x, other.x);
return *this;
}
};

使用移动语义的emplace_back()使高效率。

1
2
vector<complex<double>> v;
v.emplace_back(3, 4); // 根据构造函数

引用折叠/坍缩(Reference Collapsing)

又称左值引用传染,即如下的规则。

  • T& + & = T&
  • T& + && = T&
  • T&& + & = T&
  • T&& + && = T&&

完美转发(Perfect Forwarding)

在函数模板中,完全依照模板参数之类型,将参数传递给函数模板中调用的另一函数,称完美转发。

1
2
3
4
5
6
void check(int &) { cout << "lvalue\n"; }
void check(int &&) { cout << "rvalue\n"; }
template<typename T>
void print(T && v) {
check(std::forward<T>(v));
}

⓫继承构造函数

1
2
3
4
5
class Derived: public Base {
public:
using Base::Base;
...
};

⓫线程(Thread)

Linux下需要向编译器指明-pthread

基础

  • join():等待线程结束;
  • detach():分离执行,互不影响。

Once a thread detached, we cannot force it to join with the main thread again.

1
2
t.detach();
t.join(); // ERROR!

可使用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
2
3
4
5
void critical_section(int value) {
static std::mutex mtx;
std::lock_guard<std::mutex> lck(mtx);
// 执行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
2
3
4
// 不推荐!
mtx.lock();
// 执行value相关的竞争操作
mtx.unlock();

借此可实现经典的「生产者—消费者」场景。

下例wait()中传入lck的原因是,条件变量需要先解锁互斥量,然后阻塞,唤醒后再锁上。

1
2
3
4
mutex mtx;
condition_variable produce, consume;
queue<int> q;
const int maxSize = 20;
1
2
3
4
5
6
7
8
9
10
11
12
void producer(...) {
while(true) {
this_thread::sleep_for(chrono::milliseconds(...));
unique_lock<mutex> lck(mtx);
produce.wait(lck, []{
return q.size() != maxSize;
});
// 生产产品
q.push(...);
consume.notify_all(); // 唤醒消费者
}
}
1
2
3
4
5
6
7
8
9
10
11
12
void consumer() {
while(true) {
this_thread::sleep_for(chrono::milliseconds(...));
unique_lock<mutex> lck(mtx);
consume.wait(lck, []{
return q.size() != 0;
});
// 消费产品
q.pop();
produce.notify_all(); // 唤醒生产者
}
}

期物和诺物(std::future&std::promise)

下例中f.get()阻塞直至结果可用,等效地调用f.wait()等待结果。

1
2
3
4
5
6
7
void get(const std::future<int> & f) { int res = f.get(); ... }
void set(const std::promise<int> & p) { p.set_value(...); ... }
...
std::promise<int> p;
std::future<int> f = p.get_future(); // 关联
std::thread(get, ref(f)).join();
std::thread(set, ref(p)).join();

future可来自packaged_task

packaged_task用于封装任何可调用的目标。

1
2
3
4
std::packaged_task<int()> task([](){ return ...; });
std::future<int> f = task.get_future();
std::thread(std::move(task)).detach();
...

future可来自async()

async()的第一个参数,std::launch::async指示异步求值,std::launch::deferred指示惰性求值,默认std::launch::async|std::launch::deferred,即取决于实现。

1
2
3
std::future<int> f = std::async(std::launch::async, [](){
return ...;
});

⓫自定义字面值

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
2
template<typename... T>
auto sum(T... t) { return (t + ...); }

左折叠(...+t),其展开形如1+(2+(3+(4+5)))

右折叠(t+...),其展开形如(((1+2)+3)+4)+5

空包

参数包参数数目为0时,如sum();,会导致编译错误。

解决空包出错可以将sum()修正为如下例的二元折叠。

1
2
template<typename... T>
auto sum(T... t) { return (t + ... + 0); }

其他

使用0B书写二进制表示。

1
int a = 0B101010101010;

使用'对数字分组。

1
int b = 564'190'000;

新标准弃用的特性

字符串字面量需要const char*指向;

1
char* str = "..."; // Deprecated!

register不再具备任何含义;

auto_ptr被弃用,应使用unique_ptr