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

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

若出现错误value0

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

空包

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

解决空包出错可以将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