前言

C++11是C++程序设计语言标准的一个新的版本,在2011年由ISO批准并发布。C++11新标准从而代替了原来的C++98和C++03.。C++11标准是对C++的一次巨大的改进和扩充。在核心语法,STL标准模板等方面增加众多新功能,新亮点。

关键字以及新语法

auto关键字以及用法

auto关键字可以帮助我们分析表达式所属的类型。和以前的一些类型说明符明显不同的是,auto关键字可以让编译器自动分析某个初始值来判断它所属的类型。当然,使用auto关键字必须确定初始值。

1
2
3
4
5
6
7
8
9
10
11
vector<int> vec;
for (auto &iter : vec) //&代表引用
{
int i= iter; //iter是一个实体
}

unordered_map<uint64_t, SGroupStatDaily*>* group_stats_map_;
for(auto &iter : *group_stats_map_)
{
iter.first; //iter是一个pair类型的实体
}

注:auto作为函数返回值时,只能用于定义函数,不能用于声明函数。

decltype关键字以及用法

decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似:decltype(表达式)

在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
有时候,我们可能需要计算某个表达式的类型,例如:

1
2
3
auto x = 1;
auto y = 2;
decltype(x+y) z;

另外,decltype还可以配合auto实现拖尾返回类型,加法函数例子如下:

1
2
3
4
5
template<typename R, typename T, typename U>
R add(T x, U y)
{
return x+y;
}

使用这个模板函数的时候,必须明确指出返回类型R,所以可以使用decltype推到x+y的类型,写出这样的代码:decltype(x+y) add(T x, U y);

但是由于编译器读到 decltype(x+y) 时,x 和 y 尚未被定义,为了解决这个问题,C++11 还引入了一个叫做拖尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:

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

从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:

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

nullptr关键字及用法

在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。C++11 引入了 nullptr 关键字,专门用来区分空指针、0。

nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。

for循环语法

1
2
3
4
5
6
std::vector<int> arr(5, 100);
// & 启用了引用
for(auto &i : arr)
{
std::cout << i << std::endl;
}

STL容器

C++11在STL容器方面也有所增加,给人的感觉就是越来越完整,越来越丰富的感觉,可以让我们在不同场景下能选择跟具合适的容器,提高我们的效率。

std::array

1
2
3
4
std::array<int, 4> arr= {1,2,3,4};

int len = 4;
std::array<int, len> arr = {1,2,3,4}; // 非法, 数组大小参数必须是常量表达式

std::forward_list

std::forward_list 是一个列表容器,使用方法和 std::list 基本类似。
和 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现,只能从前往后遍历,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率,因为forward_list不用保存size以及比list保存更少的节点

std::tuple

元组的使用有三个核心的函数:

std::make_tuple: 构造元组
std::get: 获得元组某个位置的值
std::tie: 元组拆包

1
2
3
4
5
6
7
8
9
10
11
//构造
std::tuple<string, int, int> tp = std::make_tuple("st", 1, 2);

//获取
string str = std::get<0>(tp);

//拆包
string a;
int b;
int c;
std::tie(gpa, grade, name) = get_student(1);

无序容器

C++11 引入了两组无序容器:
std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant)。

智能指针

点击原文阅读。

其他

std::bind

std::bind函数是一个函数适配器,是用来绑定函数调用的某些参数的。std::bind它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体它绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制,由用户指定,要么被绑定到placeholders(占位符,如_1, _2, …, _n)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//绑定一个成员函数
class Base
{
void display_sum(int a1, int a2)
{
std::cout << a1 + a2 << '\n';
}

int m_data = 30;
};
int main()
{
Base base;
auto newFunc = std::bind(&Base::display_sum, this, 100, std::placeholders::_1);//auto也可以改为std::function<void(int, int)>
newFunc(20); //产生一个新的调用实体,执行它相当于调用display_sum()函数 should out put 120. //20即代表std::placeholders::_1
}

总结:std::bind绑定了部分参数,如例子中,绑定了参数100,最后生成的新的可调用对象(newFunc)的参数列表只剩下一个参数(std::placeholders::_1)。

std::function

类模版std::function是一种通用、多态的函数封装。通过std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,**形成一个新的可调用的std::function对象**;让我们不再纠结那么多的可调用实体。一切变的简单粗暴。

语法:std::function<函数返回值(形参1,形参2,……)>

std::function可以封装以下几种函数:

  1. 普通函数
  2. lambda表达式
  3. 函数指针
  4. 仿函数(functor 重载括号运算符实现)
  5. 类成员函数
  6. 静态成员函数
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
36
37
38
39
//普通函数
bool compare_com(int a, int b){return a > b;}
//lambda表达式
auto compare_lambda = [](int a, int b){ return a > b;};
//仿函数
class compare_class
{
public:
bool operator()(int a, int b){return a > b;}
};
//类成员函数
class compare
{
public:
bool compare_member(int a, int b){return a > b;}
static bool compare_static_member(int a, int b){return a > b;}
};

typedef std::function<bool(int, int)> callback;
int main()
{
bool b;
callback = compare_com;
b = callback(10, 1);

callback = compare_lambda;
b = callback(10, 1);

callback = compare_class();
b = callback(10, 1);

callback = compare::compare_static_member;
b = callback(10, 1);

//类普通成员函数比较特殊,需要使用bind函数,并且需要实例化对象,成员函数要加取地址符
compare temp;
callback = std::bind(&compare::compare_member, temp, std::placeholders::_1, std::placeholders::_2);
b = callback(10, 1);
}

下面通过一个例子说明std::functionstd::bind是怎么实现函数回调功能的。

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
36
37
38
39
40
#include <iostream>
#include <functional>

typedef std::function<void(std::string)> tTask;

// 线程类
class ThreadObject
{
public:
ThreadObject() {}
~ThreadObject() {}

public:
void settask(tTask task)
{
m_task = task;
}
void run()
{
// 回调执行任务函数
m_task("http://172.0.0.1/test.zip");
}

private:
tTask m_task; // std::function类型,调用者,调用回调函数
};

// 下载任务函数,也是回调函数
void downTask(std::string str)
{
std::cout << "download " << str << std::endl;
}

int main() {
ThreadObject Threadobj;
Threadobj.settask(std::bind(&downTask, std::placeholders::_1)); // 设置任务函数
Threadobj.run();

return 0;
}

std::move

https://www.cnblogs.com/keepsimple/p/3250245.html

std::transform

作用:大小写转换、两个数组元素分别相加

https://www.jianshu.com/p/cbe722ca4276

std::future

std::condition_variable

条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制,当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。典型的场景包括生产者-消费者模型,线程池实现等。

主要成员函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
通知
notify_one:通知一个等待的线程。若无线程在等待,则该函数不执行任何操作,若线程超过一个,无法指定选择哪个线程。
notify_all:通知所有等待的线程。若无线程等待,则该函数不执行任何操作

等待
wait:阻塞当前线程,直到条件变量被唤醒
wait_for:阻塞当前线程,直到条件变量被唤醒,或到指定时长后
wait_until:阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点

//wait原型有两个
void wait(unique_lock<mutex>& __lock) // 1
template<typename _Predicate>
void wait(unique_lock<mutex>& __lock, _Predicate __p) // 2

_Predicate 对象是一个判断条件是否满足的函数。1)如果调用的原型没有该参数,那么第一次调用 wait 时默认条件不成立,然后进入休眠,直至其他线程唤醒它,随后进行常规的互斥量加锁操作:获取不到锁则休眠直至重新获取到锁。2)如果调用的原型有该参数,wait 会调用该函数判断返回结果是否为 true :是,则继续执行;否,则释放互斥量并阻塞休眠,直到其他线程唤醒它,此时如果条件还不满足,则线程继续休眠等待,直到有一次唤醒后条件满足了,那么它就对互斥量加锁并继续执行后面的操作。
eg:cond_.wait(guard, [this] { return !queue_.empty();});

为了防止竞争,条件变量总是和一个互斥锁结合一起使用,如std::unique_lock<std::mutex>
wait()函数可依次拆分为三个操作:释放互斥锁、等待在条件变量上、再次获取互斥锁,即wait函数都在会阻塞时,自动释放锁权限,相当于调用unique_lock的成员函数unlock(),以便其他线程能有机会获得锁。这就是条件变量只能和unique_lock一起使用的原因,否则当前线程一直占有锁,线程被阻塞。

什么是虚假唤醒?

举个例子,我们现在有一个生产者-消费者队列和三个线程。

1) 1号线程从队列中获取了一个元素,此时队列变为空。

2) 2号线程也想从队列中获取一个元素,但此时队列为空,2号线程便只能进入阻塞(cond.wait()),等待队列非空。

3) 这时,3号线程将一个元素入队,并调用cond.notify()唤醒条件变量。

4) 处于等待状态的2号线程接收到3号线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。

5) 然而可能出现这样的情况:当2号线程准备获得队列的锁,去获取队列中的元素时,此时1号线程刚好执行完之前的元素操作,返回再去请求队列中的元素,1号线程便获得队列的锁,检查到队列非空,就获取到了3号线程刚刚入队的元素,然后释放队列锁。

6) 等到2号线程获得队列锁,判断发现队列仍为空,1号线程“偷走了”这个元素,所以对于2号线程而言,这次唤醒就是“虚假”的,它需要再次等待队列非空。

总结:虚假唤醒就是当一个正在等待条件变量的线程由于条件变量被触发而唤醒时,却发现它等待的条件(共享数据)没有满足(也就是没有共享数据)。

条件变量正确使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//全局变量
std::mutex m_mutex;
std::condition_variable m_cond;
std::queue m_que;

//wait端
lock(m_mutex);
while(que.empty()) //防止虚假唤醒
{
m_cond.wait(m_mutex);
}
//或者使用正则表达式实现,不必使用while循环
//m_cond.wait(m_mutex){return !que.empty();};
x = m_que.qoq();
unlock(m_mutex);

//signal/broadcast端
lock(m_mutex);
m_que.push_back(x);
unlock(m_mutex);
m_cond.notify_one();

std::vector的push_back与emplace_back区别

在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程(优先会使用移动构造,没有移动构造才会使用拷贝构造),所以emplace_back的效率更高。