C++ 新标准11&14

Keywords

nullptr

nullptr属于std::nullptr_t,其实就是typedef decltype(nullptr) nullptr_t,所以nullptr_t也是一种基本类型

initializer list初始化列表

使用大括号构造对象的过程:编译器首先使用大括号里的内容在只读区域构造一个临时数组array<T, N>(也可能是只是const T[N]),这个过程使用拷贝初始化,不允许类型窄化。之后构造出一个initializer_list<T>,其中只包含数组首尾指针(或者只有头指针和数组长度)。编译器会把initializer_list<T>中的元素一个一个拿出来传递给构造函数。

使用初始化列表可以实现函数变长参数,比如max({1, 2, 3}),其实就是实现了max(initializer_list<T>)函数

如果大括号里为空,将采用默认的参数;如果类里没有写相应参数的构造函数,也可以逐个对成员初始化,但前提是没有虚函数(不然会因为虚函数表的存在而导致成员变量内存偏移)

1
2
3
4
5
6
7
8
9
10
struct Obj{
int a, b;
}
Obj o = {1, 2}; // OK

struct Obj1{
virtual void F(){}
int a, b;
}
Obj1 o1 = {1, 2}; // error C2440: 'initializing': cannot convert from 'initializer list' to 'Obj1'

explicit

在旧版里可以防止当构造函数可以只接收一个参数时发生隐式类型转换,新版由于引入了initializer_list,所以对于接收多个参数的构造函数也可能发生隐式类型转换,添加explicit表示可以防止自动转换。

1
2
3
4
5
6
struct Obj{
explicit Obj(int a, int b);
};
// 下面两个调用的都是上面的构造函数,区别在于第二种会先构造一个初始化列表
Obj o{1, 2}; // 没问题
Obj o = {1, 2};// error C3445: copy-list-initialization of 'Obj' cannot use an explicit constructor

range-for

本质是使用迭代器进行遍历,对需要遍历的容器逐个取值并隐式转换为左侧声明的变量类型。如果遍历对象是一个大括号的内容,编译器将会产生一个initializer_list

=default & =delete

=default只能用于Big-Five(defualt-ctor,copy-ctor,move-ctor,dtor,operator=)默认的构造函数会隐式调用父类、非静态成员的ctor,对应dtor也是如此。默认的move-ctor只是简单地调用成员的move-ctor,如果成员没有实现move-ctor,会仍然调用copy-ctor

=delete可以用于自定义函数(=0只能用于虚函数)

using

同typedef,块作用域-类作用域-命名空间

对模板类里的别名取别名

1
using Iterator = std::list<int>::iterator;

还可以使用基类的函数,包括构造函数

Alias template

template<typename T> using Vec = vector<T, MyAlloc<T>>,自定义默认的模板参数

noexpect

如果异常没有处理就会一直向调用者传递,如果一直没有处理,最后会调用terminal,并abortnoexpect表明函数不会发出异常,move-copy和move-operator=都要标识之后才会被容器扩容时(vectordeque)使用

override & final

override:防止本来是要覆写,却不小心创建新的函数

final:对于类或结构体禁止类被继承,对于函数禁止被覆写

lambda

可以不需要参数列表,还可以直接调用:[]{ cout <<"haha"<<endl; }()定义并调用。实际上是编译器定义类,实例化函数对象,重载operator(),其中捕获的变量都是私有数据,而且当然只能捕获前面的变量。按引用捕获自然可以修改也可以接收外部的改变,而按值捕获默认是只读的,除非加mutable,实际上改变的也是自己的成员变量

set<Person,decltype(cmp)> myset(cmp)里必须使用decltype,因为模板参数需要的是类型,而传递cmp对象是因为如果不传递的话,set会实例化一个仿函数对象,然而lambda并没有default-ctor和op=,所以需要自己提供一个实例。lambda没有default-ctor可能是因为要支持变量捕获,所以对于这样容器定义的还是更推荐使用仿函数

varidic templates

需要注意省略号的位置:typename...Types...args...myFunc(std::forward<T>(args)...)

可以用sizeof...(args)获取args的参数个数

继承构造函数

声明

1
using BaseClass::BaseClass;

即可拥有像基类一样的构造函数,但是多余的成员并不会被初始化!

constexpr

真正的常量

1
2
3
4
constexpr int f(int i){
return i+1;
}
f(0); // 像这样传入编译期常量的调用就会在编译器计算出返回值

原来的const其实是readonly(可以通过const_cast修改)

Library

新增容器

array内部为原生数组,forward_list单向链表,unordered_xxx内部为哈希表并且是拉链法

chrono

多线程

thread

std::thread

1
2
3
4
5
6
7
auto t = std::thread(func, args);
if(t.joinable()){ // t与一个线程相关联
t.detach(); // 将t和关联线程分离。让主线程继续运行,但主线程结束后子线程也会退出?
// t.join(); // 等待关联线程结束。阻塞主线程直到子线程结束
}
t.get_id(); // 线程id
t.native_handle(); // 平台相关,比如用于pthread

使用ThreadGuard防止忘记调用detach或join

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ThreadGuard {
public:
enum class DesAction { join, detach };

ThreadGuard(std::thread&& t, DesAction a) : t_(std::move(t)), action_(a){};

~ThreadGuard() {
if (t_.joinable()) {
if (action_ == DesAction::join) {
t_.join();
} else {
t_.detach();
}
}
}

ThreadGuard(ThreadGuard&&) = default;
ThreadGuard& operator=(ThreadGuard&&) = default;

std::thread& get() { return t_; }

private:
std::thread t_;
DesAction action_;
};

std::this_thread

1
2
3
4
std::thread::hardware_concurrency(); // cpu个数
std::this_thread::sleep_for(std::chrono::seconds(1)); // 当前线程睡眠
// std::this_thread::sleep_until(time_point);
std::this_thread::yield(); // 建议释放控制

mutex

timed前缀的可超时,recurisive前缀的可以重入(同一线程多次加锁)

1
2
3
4
std::mutex m; // std::timed_mutex timed_m;
m.lock(); // timed_m.try_lock_for(time_duration); try_lock_until(time_point);
// ...
m.unlock();

使用RAll的封装防止忘记解锁

1
2
3
4
std::mutex m;
std::lock_guard<std::mutex> lock(m); // lock_guard构造即加锁,析构即解锁
std::unique_lock<std::mutex> lock(m, method); // unique_lock可以手动加解锁
// method可以是std::adopt_lock、std::defer_lock、std::try_lock表示m已加锁、不给m加锁、尝试给m加锁(可以改为传递time_duration或time_point)

condition_variable

1
2
3
4
5
std::condition_variable cv;
cv.wait(my_unique_lock); // cv.wait(my_ulock, []{ return q.empty(); }); // 满足额外的条件才唤醒
// cv.wait_for(my_unique_lock, std::chrono::milliseconds(10));
cv.notify_one();
// cv.notify_all();

future

1
2
3
auto res = std::async(std::launch::async, func, args); // 单独线程执行,std::launch::deferred是在调用wait/get时才非异步执行
// res.wait();
res.get(); // 阻塞直到返回

atomic

原子操作保证了对数据的访问只有未开始和已完成两种状态

声明原子变量:std::atomic<int> count

std::atomic_flag是保证无锁的bool变量, 只能test_and_setclear

lock_free_stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
template<typename T>
class lock_free_stack
{
private:
struct node
{
T data;
node* next;
node(const T& data) : data(data), next(nullptr) {}
};
std::atomic<node*> head;

public:
lock_free_stack(): head(nullptr) {}
void push(const T& data)
{
node* new_node = new node(data);
do{
new_node->next = head.load(); //将 head 的当前值放入new_node->next
}while(!head.compare_exchange_strong(new_node->next, new_node));
// 如果新元素new_node的next和栈顶head一样,证明在你之前没人操作它,使用新元素替换栈顶退出即可;
// 如果不一样,证明在你之前已经有人操作它,栈顶已发生改变,该函数会自动更新新元素的next值为改变后的栈顶;
// 然后继续循环检测直到状态1成立退出;
}
T pop()
{
node* node;
do{
node = head.load();
}while (node && !head.compare_exchange_strong(node, node->next));

if(node)
return node->data;
}
};

Compiler Optimization

返回值优化RVO

T类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
class T {
public:
T(const std::string& name): _name(name) { std::cout << _name << " ctor\n"; }
T(const T&) { std::cout << _name << " copy ctor\n"; }
T(T&&) { std::cout << _name << " move ctor\n"; }
T& operator= (const T&) { std::cout << _name << " copy assign\n"; return *this; }
T& operator= (T&&) { std::cout << _name << " move assign\n"; return *this; }

~T() { std::cout << _name << " dtor\n"; }

private:
std::string _name;
};

测试1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
T FT(const std::string& name) {
T t(name);
return t; // 返回局部变量
}

{
T t = FT("1");
}
std::cout << "**********\n";
{
auto&& tt = FT("2");
}
/*输出
1 ctor
move ctor
1 dtor
dtor
**********
2 ctor
move ctor
2 dtor
dtor
*/
  • 没有返回值优化时需要经过“return到临时变量,临时变量到接收变量”,可能需要两次的拷贝,其中第二次因为是右值转移,所以是肯定移动构造。

  • 有返回值优化之后,第一次的拷贝也被省掉了。

值得注意的是,第二种写法也需要一次移动构造?也许并不需要手动写成右值引用去接收

下面是直接返回临时变量的测试,连移动构造都省掉了

测试2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
T FT(const std::string& name) {
return T(name); // 返回临时变量
}

{
T t = FT("1");
}
std::cout << "**********\n";
{
auto&& tt = FT("2");
}
/*输出
1 ctor
1 dtor
**********
2 ctor
2 dtor
*/