第一篇 C++11 改进我们的程序

第一章 使用C++11让程序更简洁,更现代化

一.类型推导
1.auto类型推导

auto关键字被赋予了新的意义,c++98及之前代表”具有自动存储器的局部变量”,且用处不大,现在用来进行类型推导,简化了代码

1
2
3
4
5
6
7
//#示例
auto x = 5; //x -> int
auto pi = new auto(1); //pi -> int*
const auto *v = &x, u = 6; //v -> const int* u->int
static auto y = 0.0; //y -> double
auto int r; //r -> 旧标准的意义被废除
auot s; //s -> 无法推导

auto的推导,先引出一组例子

1
2
3
4
5
6
7
8
9
10
11
12
int x = 0;

auto * a = &x; // a->int* auto->int
auto b = &x; // b->int* auto->int*
auto & c = x; // c->int& auto->int
auto d = c; // d->int auto->int

const auto e = x; // e->const int auto->int
auto f = e; // f->int auto->int

const auto& g = x; // g->const int&
auto& h = g; // h->const int&
  1. 当不声明为指针或引用时,auto推导时将抛弃cv限定符
  2. 当声明为指针或引用时,auto推导时将保持cv属性

auto的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void func(auto a = 1){}              //error 不能用于函数参数

struct Foo
{
auto var1 = 0; //error 不能用于non-static成员
static const auto var2 = 0; //ok var2 -> static const int
};

template <typename T>
struct Bar {};

int main()
{
int arr[10] = {0};
auto aa = arr;
auto rr[10] = arr; //error 不能用于构建数组

Bar<int> bar;
Bar<auto> bb = bar; //error 不能用于模板参数

return 0;
}

何时使用auto

最常用的地方应该就是迭代器类型的推导,一般容器的类型已经很长了,再加上嵌套从属名称就更长了,auto可以有效进行这种类型代码的简化,已经获得函数返回值时,也可以直接用auto定义变量

2.decltype关键字

该关键字和auto有点类似,两者一起使用更是加大了类型推导的灵活度,与auto相比,auto像是一个占位符,而decltype确实就像是类型

获知表达式的类型,引出一组例子

1
2
3
4
5
6
7
8
9
10
11
12
//#示例           decltype(exp) 

int x = 0;
decltype(x) y = 1; // y ->int
decltype(x + y) z = 0; // z ->int

const int& i = x;
decltype(i) j = y; // j ->const int&

const decltype(z) * p = &z; //*p ->const int p->const int*
decltype(z) * pi = &z; //*pi->int pi->int*
decltype(pi)* pp = &pi; //*pp->int* pp->int **

decltype的推导规则

  1. exp是标识符,类访问表达式,推导的类型和exp一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Foo
    {
    public:
    static const int Number = 0;
    int x;
    };

    int n = 0;
    volatile const int & x = n;

    decltype(n) a = n; // a->int
    decltype(x) b = n; // b->volatile const int&

    decltype(Foo::Number) c = 0; // c->const int

    Foo foo;
    decltype(foo.x) d = 0; // d->int 类访问表达式
  2. exp是函数调用,推导的类型和返回值类型一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    int& func_int_r(void);            //左值
    int&& func_int_rr(void); //x值
    int func_int(void); //纯右值

    const int& func_cint_r(void); //左值
    const int&& func_cint_rr(void); //x值
    const int func_cint(void); //纯右值

    const Foo func_cfoo(void); //纯右值

    int x = 0;

    decltype(func_int_r()) a1 = x; //a1->int &
    decltype(func_int_rr()) b1 = 0; //b1->int &&
    decltype(func_int()) c1 = 0; //c1->int

    decltype(func_cint_r()) a2 = x; //a2->const int &
    decltype(func_cint_rr())b2 = 0; //b2->const int &&
    decltype(func_cint()) c2 = 0; //c2->int

    decltype(func_cfoo()) ff = Foo(); //ff->const Foo

    由此可以看出规则2,decltype的结果和函数的返回值类型一致,唯一需要注意的点在,返回值为纯右值的情况下,只有类类型可以携带cv限定符,此外一半忽略cv限定,如c2和ff对比

  3. 其他情况,若exp是一个左值,则推导的类型为左值引用,否则和exp类型一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //#带括号的表达式和加法运算符

    struct Foo {int x;};
    const Foo foo = Foo();

    decltype(foo.x) a = 0; //a->int
    decltype((foo.x)) b = a; //b->const int &

    int n = 0, m = 0;
    decltype(n + m) c = 0; //c->int
    decltype(n +=m) d = c; //d->int &

    a根据规则1推导,标识符的类型也即是int, b,c,d根据规则3推导,a,d为左值,推导为左值引用,c为右值,推导为int

decltype的实际应用

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
41
42
43
44
//#下面示例

template <class ContainerT>
class Foo
{
//为什么用typename? Effective 条款42 说过
//嵌套从属类型默认情况下不是类型,需要typename注明它是一个类型

typename ContainerT::iterator it_; //类型定义可能又问题
public:
void func(ContainerT& container)
{
it_ = container.begin();
}
...
};
int main()
{
typedef const std::vecotr<int> container_t;
container_t arr;

Foo<container_T> foo;
foo.func(arr);

return 0;
}

//此处的的错误直接看很难看出,但是foo中的只是一个普通的迭代器,但传入的是const 容器
//应当使用const_iterator , 想解决这个问题, c++98/03中只能用模板特化,此处不举例了
//decltype可以很好的解决这个问题

template <class ContainerT>
class Foo
{
decltype(ContainerT().begin()) it_;
public:
void func(ContainerT& container)
{
it_ = container.begin();
}
...
};
//开始我有一个疑问auto行嘛,显然是不行的auto 应该是一种在初始化的时候推导的,需要由后面的推导

3.返回类型后置语法—-auto和decltype的结合使用

这块通过例子理解就行

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
41
42
//#第一个场景
template <typename R,typename T,typename U>
R add(T t, U u)
{
return t+u;
}

int a = 1; float b = 2.0;
auto c = add<decltype(a+b)>(a,b);
//这样相当于调用前,及给模板参数R确定了返回值类型,很不方便

//第二次改进
template <typename T,typename U>
decltype(t+u) add(T t,U u)
{
return t+u;
}
//这种写法就错误了,返回值是前置语法,定义的时候,参数变量还不存在

//第三次改进
template <typename T,typename U>
decltype(T()+U()) add(T t,U u)
{
return t+u;
}
//目前可行了,但考虑无参构造函数,应再改进一次

//第四次改进
template <typename T,typename U>
decltype((*(T*)0)+(*(U*)0)) add(T t,U u)
{
return t+u;
}
//这就是完全正确的写法,不过应该都感受到了,太离谱了

//第五次改进 本小节内容的 auto+decltype 返回类型后置语法
template <typename T,typename U>
auto add(T t,U u) -> decltype(t+u)
{
return t+u;
}
//另一个例子见 书p15
二.模板的细节改进
1.模板的右尖括号

这里主要是一个细节上的优化,直接看例子就行了

1
2
3
4
5
6
7
8
9
template <class T>
struct Foo {int x;};
template <class T>
class A {int x;};

Foo<A<int>> x; //这样写以前会报错,需要改为 Foo<A<int> > x; 现在不会
//不过但来的另一个差异就是
Foo<100 >> 2 > x; //现在不行了,但以前可以,现在需要用括号括起来
Foo<(100>>2)> x; //所以说括号真是个好东西
2.模板的别名

也没啥好说的 using 替代了 typedef 而且using还能处理模板时的别名,而typedef不行

1
2
3
4
5
6
7
8
9
10
11
12
13
//给出一个对比示例
/* c++98/03 */
template <typename T>
struct func_t
{
typedef void (*type)(T,T);
};
func_t<int>::type xx_1;

/* c++11 */
template <typename T>
using func_t = void (*)(T,T);
func_t<int> xx_2;
3.函数模板的默认参数模板

C++98/03时只支持类模板的默认参数,不支持函数的默认模板参数,C++11中这限制被解除,而且类模板和函数模板有点差异,当所有模板参数都有模板时,函数模板的调用如同一个普通函数,但类模板即使都有默认参数,还是需要在模板名后跟上”<>”来实例化

这里的一些关键点在于,模板参数的自动填充推导

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
//第一个例子
template <typename R = int,typename U>
R func(U val)
{
ret val;
}
int main()
{
func(123);
return 0;
}

func<long>(123); //func的返回值类型是 long

//第二个例子
template <typename T>
struct identity
{
typedef T type;
};

template <typename T = int>
void func(typename identity<T>::type val, T = 0)
{
...
}
int main()
{
func(123);
func(123,123.0);
return 0;
}

显示指定模板参数类型,模板参数的填充顺序,从左往右,第二个例子外敷模板禁用参数的自动推导

三.列表初始化
1.统一的初始化

之前只能用于普通数组和POD类型,现在可以用于所有类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Foo
{
public:
Foo(int) {}
private:
Foo(const Foo &);
};

int main()
{
Foo a1(123);
Foo a2 = 123; //error: 需要copy 构造
//此处需要copy,但感觉上直接调用了Foo(int)构造的原因,编译的优化问题
//先生成一个临时对象,再copy a2,但经过优化后,直接调用 FOO(int)的构造就完事了

Foo a3 = {123};
Foo a4 {123};

int a5 = {3};
int a6 {3};

return 0;
}
2.列表初始化的使用细节

只能对聚合类型使用

  1. 聚合类型是一个普通数组
  2. 聚合类型一个类且,无自定义的构造,无私有保护非静态数据成员,无基类,无虚函数,不能有直接初始化的数据

对于无自定义的构造这条,可能会觉得不是有构造的时候也可以吗,实际上此时用初始化列表,调用了构造函数,这也是对于非聚合类型,使用初始化列表的方法

3.初始化列表

C++11中的STL容器拥有和未指定长度的数组一样的能力

1
2
3
4
5
6
7
int arr[] {1,2,3};

std::map<std::string,int> mm= { {"1",1}, {"2",2} , {"3",3} };

std::set<int> ss = {1,2,3};

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

这种能力来自于 std::initializer_list 这个轻量级的类模板,对于我们自定义的类,只要添加这个,也能拥有这个功能

1
2
3
4
5
6
class Foo
{
public:
Foo(std::initializer_list<int>) {}
};
Foo foo = {1,2,3,4,5}; // ok

再举出一个使用的例子,给自定义的容器赋值

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
class FooVecor
{
std::vector<int> content_;
public:
FooVector(std::initializer_list<int> list)
{
for (auto it = list.begin(); it!=list.end(); ++it)
{
content_.push_back(*it);
}
};
}
class FooMap
{
std::map<int,int> content_;
using pair_t = std::map<int,int>::value_type;
public:
FooMap(std::initializer_list<pair_t> list)
{
for (auto it = list.begin(); it!=list.end(); ++it)
{
content_.insert(*it);
}
}
};

FooVector foo_1 = {1,2,3,4,5};
FooMap foo_2 = {{1,2},{3,4},{5,6}};

std::initializer_list的一些细节

  1. 轻量级的容器类型
  2. 储存的元素必须是同种类型
  3. 3个接口, size begin end
  4. 只能被整体初始化或赋值
  5. 传递效率高,因为保存的是元素的引用,因为是引用所以也不能,保存局部变量
4.防止类型收窄

看看就好,p33面图

四.基于范围的for循环
1.for循环的新用法

与一般的for遍历,和for_each便利,不同给出了一种新的遍历方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#inclued <iostream>
#include <vector>

int main()
{
std::vector<int> arr = {1,2,3};

//利用auto 推导类型,也可以直接写上类型
//此写法为只读
for (auto n : arr)
{
std::cout << n << std::endl;
}

//如果想要修改容器中的元素
for (auto& n : arr)
{
std::cout << n++ << std::endl;
}

return 0;
}
2.基于范围的for循环的使用细节
  1. 使用时注意容器本身的约束,如set和map容器是不支持修改的,如果用auto & 则会推到为带const的

  2. 在迭代时修改容器会发生什么

    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
    #include <iostream>
    #include <vector>

    int main()
    {
    std::vector<int> arr={1,2,3,4,5};
    for (auto val : arr)
    {
    //导致迭代器失效了
    std::cout << val << std::endl;
    arr.push_back(0);
    }
    return 0
    }
    //执行结果: 自己的编译器上
    //1
    //565
    //1936136848
    //565
    //-17891602

    #include <iostream>
    #include <vector>

    int main()
    {
    std::vector<int> arr = {1,2,3,4,5};

    //于基于范围的for循环等价的普通for循环,可以看出,开始前就已经确定了迭代范围
    auto && _range = (arr);
    for (auto _begin = _range.begin(),_end=_range.end();
    _begin != _end; ++ _begin)
    {
    auto val = * _begin;
    std::cout << val << std::endl;
    arr.push_back(0);
    }
    return 0;
    }
3.让基于范围的for循环支持自定义类型
  1. 对于普通的array对象,begin为首地址,end为首地址+容器长度
  2. 对于类对象,range-based for 查找类的begin() 和 end() 来确定,如同书中的例子
  3. 否则采用全局的begin 和 end函数来确定(不清楚这条)

书中给出了一个例子,自己实现,range函数返回一个自己定义的容器,底层由一个迭代器类实现,impl为范围for迭代的容器

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <iostream>

namespace detail_range
{
//迭代器细节
template<typename T>
class iterator
{
public:
using value_type = T;
using size_type = size_t;

private:
size_type cursor_; //用来!=中进行比较的,因为可能存在约数,不能正常比较!=,在end 和 begin的时候就计算范围
const value_type step_; //步长
value_type value_; //值

public:
iterator(size_type cur_start, value_type begin_val, value_type step_val)
:cursor_(cur_start), step_(step_val), value_(begin_val)
{
//计算初始的值
value_ += (step_ * cursor_);
}

value_type operator*() const { return value_; }

bool operator!=(const iterator& rhs)const
{
//比较是否还在迭代范围中
return (cursor_ != rhs.cursor_);
}

//基于范围的for循环中需要 prefix ++
iterator& operator++()
{
value_ += step_;
++ cursor_;
return (*this);
}
};

//impl实现
template <typename T>
class impl
{
public:
using value_type = T;
using reference = const value_type&;
using const_reference = const value_type&;
using iterator = const detail_range::iterator<value_type>;
using const_iterator = const detail_range::iterator<value_type>;
using size_type = typename iterator::size_type;

private:
const value_type begin_;
const value_type end_;
const value_type step_;
const size_type max_count_;

size_type get_adjusted_count()const
{
//判断迭代范围是否正常
if (step_>0 && begin_ >=end_)
throw std::logic_error("End value must be greater than begin value.");
else if(step_<0 && begin_<=end_)
throw std::logic_error("End value must be less than begin value.");

//计算迭代次数
auto x = static_cast<size_type>((end_-begin_)/step_);

//处理一些边界问题吧?
if (begin_ + (step_*x) != end_) ++x;
return x;
}

public:

impl(value_type begin_val, value_type end_val, value_type step_val)
:begin_(begin_val) , end_(end_val)
,step_(step_val) , max_count_(get_adjusted_count())
{}

size_type size() const
{
return max_count_;
}

const_iterator begin()const
{
//返回一个底层的迭代器,用到了列表初始化,如果不用这个,需要先创建一个临时变量,然后传过去
return {0, begin_, step_};
}
const_iterator end() const
{
return {max_count_,begin_,step_};
}
};
//namespace detail_range
}


//range的不同情况
template<typename T>
detail_range::impl<T> range(T end)
{
//处理一个参数,15,从0开始,直到15
return {{},end,1};
}

template <typename T>
detail_range::impl<T> range(T begin, T end)
{
return {begin,end,1};
}

//感觉上这个U好像没啥用,Impl的参数类型已经由begin+step确定了
template<typename T,typename U>
auto range(T begin,T end, U step) -> detail_range::impl<decltype(begin+step)>
{
using r_t = detail_range::impl<decltype(begin+step)>;
return r_t(begin,end,step);
}

void test_range()
{
using std::endl;
using std::cout;

cout << "Range(15):";
for (int i : range(15))
{
cout << " " << i;
}
cout << endl;

cout << "Range(2,6):";
for (auto i : range(2,6))
{
cout << " " << i;
}
cout << endl;

const int x = 2, y = 6 , z = 3;
cout << "Range(2,6,3):";
for (auto i : range(x,y,z))
{
cout << " " << i;
}
cout << endl;
}

int main() {
test_range();
return 0;
}

对此处的设计还是有点不清楚,只大致看懂了实现流程,可能水平不够

五.function和bind绑定器
1.可调用对象
  1. 是一个函数指针
  2. 是一个具有operator()的函数对象(仿函数)
  3. 可被转换成函数指针的类对象(用转换函数实现)
  4. 是一个类成员(函数)指针
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void func() {...}

struct Foo
{
void operator()
{
...
}
};

struct Bar
{
using fr_t = void(*)(void);

static void func(void)
{
...
}

operator fr_t()
{
return func;
}
};

struct A
{
int a_;

void mem_func()
{
...
}
};

int main()
{
//1.函数指针
void (* func_ptr)(void) = & func;
func_ptr();

//2.函数对象
Foo foo;
foo();

//3.可被转换为函数指针的类对象
Bar bar;
bar();

//4.类成员函数指针 和 类成员指针
void (A::*mem_func_ptr)(void) = &A::mem_func;
int A::*mem_obj_ptr = &A::a_;

A aa;
(aa.*mem_func_ptr)();
aa.*mem_obj_ptr = 123;

return 0;
}
2.可调用对象包装器—-std::function

简单来说用来容纳除了类成员指针和类成员函数指针的一个类模板,相当于对上述各种不同的可调用对象,用一个统一的方式管理

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
41
42
43
44
45
46
#include <iostream>
#include <functional>

void func()
{
//__FUNCTION__是一个宏 ,返回函数名(字符串)
std::cout << __FUNCTION__ << std::endl;
}

class Foo
{
public:
static int foo_func(int a)
{
std::cout << __FUNCTION__ << "(" << a << ") ->: ";
return a;
}
};

class Bar
{
public:
int operator()(int a)
{
std::cout << __FUNCTION__ << "(" << a << ") ->: ";
return a;
}
};

int main()
{
//绑定一个普通函数
std::function<void(void)> fr1 = func;
fr1();

//绑定一个类的静态成员函数
std::function<int(int)> fr2 = Foo::foo_func;
std::cout << fr2(123) << std::endl;

//绑定一个函数对象
Bar bar;
fr2 = bar;
std::cout << fr2(123) << std::endl;

return 0;
}

std::function需要的只是合适的函数前面,返回值和参数表,即可容纳所有符合的函数

一个回调函数的例子

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
#include <iostream>
#include <functional>

class A
{
std::function<void()> callback_;

public:
A(const std::function<void()>& f)
:callback_(f)
{}

void notify(void)
{
callback_();
}
};

class Foo
{
public:
void operator()(void)
{
std::cout << __FUNCTION__ << std::endl;
}
};

int main()
{
Foo foo;
A aa(foo);
aa.notify();

return 0;
}

还有一个例子,和上面的差不多,可以看出包装器,比起普通函数指针更加方便灵活

3.std::bind绑定器

主要两个用途 1.将可调用对象及其参数一起绑定成一个函数对象 2.将多元可调用对象,转换成一元或n-1元

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//#使用例子 1.基本用法
#include <functional>
#include <iostream>

void call_when_even(int x, const std::function<void(int)>& f)
{
if (!(x & 1)) //x%2 == 0
{
f(x);
}
}

void output(int x)
{
std::cout << x << " ";
}

void output_add_2(int x)
{
std::cout << x + 2 << " ";
}

//placeholers 占位符 _1 就是从第一个参数补到这里
int main()
{
{//括号作用域 注意 析构掉fr 方便重新用fr定义,否则编译错误
auto fr = std::bind(output, std::placeholders::_1);

for (int i = 0; i < 10; i++)
{
call_when_even(i, fr);
}
}

std::cout << std::endl;

auto fr = std::bind(output_add_2, std::placeholders::_1);
for (int i = 0; i < 10; i++)
{
call_when_even(i, fr);
}

system("pause");
return 0;
}
//使用例子 2.std::bind的占位符
#include <iostream>
#include <functional>

void output(int x,int y)
{
std::cout << x << " " << y << std::endl;
}

int main()
{
std::bind(output,1,2)(); //-> 1,2
std::bind(output, std::placeholders::_1, 2)(1); //-> 1,2
std::bind(output, 2, std::placeholders::_1)(1); //-> 2,1

//error 调用时并没有传入第二个参数
std::bind(output, 2, std::placeholders::_2)(1);
//第一个参数被2占用
std::bind(output, 2, std::placeholders::_2)(1,2); //-> 2,2

std::bind(output, std::placeholders::_1, std::placeholders::_2)(1, 2);//-> 1,2
std::bind(output, std::placeholders::_2, std::placeholders::_1)(1, 2);//-> 2,1

return 0;
}

其次就是之前提到的和function组合使用

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
#include <iostream>
#include <functional>

class A
{
public:
int i_ = 0;

void output(int x,int y)
{
std::cout << x << " " << y << std::endl;
}
};

int main()
{
A a;
std::function<void(int,int)> fr = std::bind(&A::output,&a,
std::placeholders::_1,std::placeholders::_2);
fr(1,2); //输出 1 2

std::function<int&(void)> fr_i = std::bind(&A::i_,&a);
fr_i = 123;
std::cout << a.i_ << std::endl; //输出 123

return 0;
}

将A的成员函数output的指针与a绑定,转换成一个仿函数放入包装器

另外两个用途

  1. 一起版本的bind1st 和bind2nd 现在可以被,bind用不同位置的占位符取代
  2. 使用组合bind函数,如大于5小于10 可以将这两个函数和一个逻辑与函数绑在一起实现
六.lambda表达式
1.lambda
1
[ capture ] (params) opt -> ret { body; };

capture 为捕获方式, params为参数列表 , opt为函数选项, ret为返回类型

如下为一个完整的lambda表达式例子

1
2
3
4
5
6
7
auto f = [](int a) -> int {return a+1; };
std::cout << f(1) << std::endl;
//由于lambda表达式的返回值通常比较明显,C++11允许省略lambda表达式的返回值
auto f = [](int a) {return a+1; };

auto x1 = [](int i) {return i;};
auto x2 = []() { return {1,2} ;}; //error: 初始化列表不能用于返回值的推导

lambda表达式捕获不同方式:

  1. [] 不捕获任何变量
  2. [&] 按引用捕获所有变量
  3. [=] 按值捕获所有变量
  4. [=,&foo] 按值捕获所有变量,按引用捕获foo变量
  5. [bar] 只按值捕获bar变量
  6. [this] 捕获this指针,lambda拥有和类成员相同的访问权

一个注意点:按值捕获的变量,在lambda表达式是不能修改的,也就是说以下例子中的用法是不行的

1
2
3
int a = 0;
auto f1 = [=]{return a++; }; //error , 修改按值捕获的变量
auto f2 = [=]() mutable { return a++; }; //ok , mutable

其他一些注意的点

  1. 可以用std::bind 和 std::function来存储和操作lambda表达式
  2. 对于没有捕获任何变量的lambda表达式,可以被转换成一个普通的函数指针

对于为什么按值捕获的变量不能修改

书中的解释是,lambda和基于范围的for类似,都是一种语法糖,实质还是一个函数对象,且lambda表达式默认的operator()是const的,而捕获的值相当于成员变量,这也是为什么不能修改,而mutable的作用就是取消const

2.声明式的编程风格,简洁的代码

替换掉函数对象的定义,直接就地定义并实现

3.在需要的时间和地点实现闭包,使程序更灵活

如前文std::bind时提到的大于5小于10的函数,此时能够更简洁的实现

1
int count = std::count_if(coll.begin(),coll.end(),[](int x) {return x>5 && x<10;});

且增加了可读性,一眼就能看出此处函数的功能以及实现

七.tupe元组

使用上相当于一个普通的结构体,直接看用法就完事了

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
//1.创建一个tuple
tuple<const char*,int> tp = make_tuple(sendPack,nSendSize); //构造一个tuple
//等价于一个结构体
struct A
{
char * p;
int len;
};

//2.另一种创建方式 是用std::tie,它会创建一个元组的左值引用
int x = 1;
int y = 2;
string s = "aa";
auto tp = std::tie(x,s,y);
//tp的实际类型为 tuple<int&,int&,string&>

//3.第一种获取元组的方式
const char * data = tp.get<0>(); //获取第一个值
int len = tp.get<1>(); //获取第二个值

//4.第二种方式 通过std::tie 解包 tuple
int x,y;
string a;
std::tie(x,a,y) = tp;
//通过占位符 std::ignore 可以获取我们想要的值
std::tie(std::ignore,std::ignore,y) = tp; //只获取第三个值

//5.创建右值引用元组的方式 forward_as_tuple
std::map<int,std::string> m;
m.emplace(std::piecewise_construct,std::forward_as_tuple(10),
std::forward_as_tuple(20,'a'));
//piecewise_construct pair 的方法
//这里forward_as_tuple创建了相当于 <int&&,std::string&&>的tuple

//6.tuple_cat
八.总结

本章主要介绍的特性简化代码,auto和decltype的配合使用方便获取返回值类型,模板的别名,方便减少琐碎的代码,列表初始化统一初始化的方式,range for 更简单的遍历容器,function 和 bind 灵活的对函数操作,tupl元组,替代简单的结构体,lambda表达式,就地定义匿名函数,提高可读性

第二章 使用C++11改进程序性能

一.右值引用

C++11新增的类型,右值引用. C++中所有的值都属于左值和右值之一,右值又分为将亡值(x-value)和纯右值

1.&&的特性

因为右值不具名,只能通过引用的方式来找到它

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
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
using namespace std;

int g_constructCount = 0;
int g_copyConstructCount = 0;
int g_destructCount = 0;

struct A
{
A()
{
cout << "construct: " << ++g_constructCount << endl;
}
A(const A& a)
{
cout << "copy construct: " << ++g_copyConstructCount << endl;
}
~A()
{
cout << "destruct: " << ++g_destructCount << endl;
}
};

A GetA()
{
return A();
}

int main()
{
A a = GetA();
return 0;
}
//输出结果:
// construct: 1
// copy construct: 1
// destruct: 1
// copy construct: 2
// destruct: 2
// destruct: 3

//如果改为右值引用
int main()
{
A&& a = GetA();
return 0;
}
//输出结果:
// construct: 1
// copy construct: 1
// destruct: 1
// destruct: 2

通过右值引用,减少了一次析构和拷贝构造,利用这个特点做到了性能优化,同样,常量的左值引用也能用来这样

1
const A& a = GetA();

常量的左值引用相当于万能引用,既能接受左值也能接受右值,但普通的左值引用只能接收左值

universal reference , 当发生自动推导(模板的类型或auto) , &&的类型不确定

1
2
3
4
5
6
template <typename T>
void f(T&& param);

f(10); //10右值
int x = 10;
f(x); //x左值

这种变化被称为引用折叠:

  1. 所有的右值叠加到右值引用上仍然还是右值引用
  2. 所有的其它引用类型之间的叠加都将变成左值引用

注意这种引用仅出现在 自动推导 以及 **T&&**下,其他任何一点附带条件都会使之失效,如:

1
2
3
4
template <typename T>
void f(std::vector<T>&& param)
template <typename T>
void f(const T&& param);
1
2
3
4
//对于左值,可以用std::move将其变为右值
int w1,w2;
decltype(w1)&& v2 = w2; //error 将左值传给右值引用
decltype(w1)&& v2 = std::move(w2); //ok

注意:编译器会将已命名的右值引用视为左值,而将未命名的右值引用视为右值

2.右值引用优化性能,避免深拷贝

也就是用移动构造,常规的深拷贝是利用拷贝构造重新分配空间,然后复制资源,移动构造则采用转移资源的拥有权,采用浅拷贝的方式,减少不必要的临时对象的创建和销毁

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class MyString
{
private:
char* m_data;
size_t m_len;
void copy_data(const char *s)
{
m_data = new char[m_len+1];
memcpy(m_data,s,m_len);
m_data[m_len] = '\0';
}

public:
MyString()
{
m_data = NULL;
m_len = 0;
}
MyString(const char* p)
{
m_len = strlen(p);
copy_data(p);
}
//拷贝构造 在堆中重新开辟空间 复制资源
MyString(const MyString& str)
{
m_len = str.m_len;
copy_data(str.m_data);
}
MyString& operator=(const MyString& str)
{
if (this != &str)
{
m_len = str.m_len;
copy_data(str.m_data);
}
return *this;
}
//移动构造
MyString(MyString&& str)
{
m_len = str.m_len;
m_data = str.m_data;
str.m_len = 0;
str.m_data = nullptr;
}
MyString& operator=(MyString&& str)
{
if (this != &str)
{
m_len = str.m_len;
m_data = str.m_data;
str.m_len = 0;
str.m_data = nullptr;
}
return *this;
}

~MyString()
{
if (m_data)
delete [] m_data;
}
}
二.move语义

仅仅转移资源的所有者,将资源的拥有者改为被赋值者,这就是所谓的move语义

总而言之,就是为了让左值在特定情况下使用移动构造或移动=operator , 来减小不必要的开下,此时就会用到std::move来讲一个左值强制转为一个右值引用

三.forward完美转发
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
using namespace std;

void PrintT(int& t)
{
cout << "lvalue" << endl;
}

template <typename T>
void PrintT(T&& t)
{
cout << "rvalue" << endl;
}

template <typename T>
void TestForward(T&& v)
{
PrintT(v);
PrintT(std::forward<T>(v));
PrintT(std::move(v));
cout << endl;
}


int main()
{
TestForward(1);
//传入后 T int&&
int x = 1;
TestForward(x);
//传入后 T int&
TestForward(std::forward<int>(x));
//传入后 T int&&
TestForward(std::forward<int&>(x));
//传入后 T int&

system("pause");
return 0;
}
//输出结果
//lvalue
//rvalue
//rvalue

//lvalue
//lvalue
//rvalue

//lvalue
//rvalue
//rvalue

//lvalue
//lvalue
//rvalue

//这里注意的一点是,完美转发时,类型是按照模板中的类型,也就是<>中的,与后面的实参(x)无关,所以第三个例子,传入后是T&& 而不是 T&,因为是根据int转发的而不是根据左值x转发的

万能函数包装器,后续看完3.2来补充

1
std::cout << "hello,world" << std::endl;
四.emplace_back减少内存拷贝和移动

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <vector>
#include <iostream>
using namespace std;
struct A
{
int x;
double y;
A(int a,double b):X(a),y(b) {}
};
int main()
{
vector<A> v;
v.emplace_back(1,2);
cout << v.size() << endl;
return 0;
}

对比

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std;

struct Complicated
{
int year;
double country;
string name;

Complicated(int a,double b,string c):year(a),country(b),name(c)
{
cout << "is constructed" <<endl;
}
Complicated(const Complicated& other):year(other.year),country(other.country),
name(std::move(other.name))
{
cout << "is moved" <<endl;
}
};

int main()
{
map<int,Complicated> m;
int anInt = 4;
double aDouble = 5.0;
string aString = "C++";
cout << "--insert--" <<endl;
m.insert(std::make_pair(4,Complicated(anInt,aDouble,aString)));

cout << "--emplace--" <<endl;
m,emplace(4,Complicated(anInt,aDouble,aString));

cout << "--emplace_back--" <<endl;
vector<Complicated> v;
v.emplace_back(anInt,aDouble,aString);

cout << "--push_back--" <<endl;
v.push_back(Complicated(anInt,aDouble,aString));
}

//输出如下
//--insert--
//is constructed
//is moved
//is moved
//--emplace--
//is constructed
//is moved
//--emplace_back--
//is constructed
//--push_back--
//is constructed
//is moved
//is moved
五.unordered container 无序容器

C++11增加了无序容器,unordered_map / unordered_multimap 和 unordered_set / unordered_multiset,这些容器不排序,因此会比有序版本的效率更高,map和set内部是红黑树,而无序容器内部是散列表,

1
2
//由于map没了解过,这里对于无序容器的用法先放一下,后续再来补充
std::cout << "hello,world" << std::endl;
六.总结

1.右值引用仅仅是通过改变资源的所有者来避免拷贝内存,能大幅度提高性能

2.forward能根据参数的实际类型转发给正确的函数

3.emplace系列函数通过直接构造对象的方式避免了内存的拷贝和移动

4.无序容器在插入元素时不排序,提高了插入效率,不过对于自定义的key时需要提供hash函数和比较函数

第三章 使用C++11消除重复,提高代码质量

一.type_traits—-类型萃取

在Effective C++中也提到过一次这个,感觉不是很好理解,目前理解就是帮助在编译期一些操作的工具,用到模板的特性来实现?

1.基本的type_tratis
  1. 简单的traits

    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
    //实现编译期常量
    //c++11前
    //1.静态成员常量
    template <class T>
    struct GetLeftSize
    {
    static const int value = 1;
    };
    //2.枚举
    template <typename T>
    class GetLeftSize
    { //关于枚举常量,最近发现的一个好处,在switch时,使判断条件可读性更高
    enum{value=1};
    };
    //c++11
    //3.traits
    template <typename Type>
    struct GetLeftSize:std::integral_constant<int,1>
    {};

    //实现
    template <typename T,T t>
    struct integral_constant
    {
    static const T value = t;
    typedef T value_type;
    typedef integral_constant<T,t> type;
    operator value_type (){return value;}
    };
  2. 类型判断的type_traits

    1
    2
    3
    4
    5
    6
    7
    cout << "----two----"  <<endl;
    cout << "is_const:" << endl;
    cout << "int: " << std::is_const<int>::value << endl;
    cout << "const int: " << std::is_const<const int>::value << endl;
    cout << "const int&: " << std::is_const<const int&>::value << endl;
    cout << "const int*: " << std::is_const<const int*>::value << endl;
    cout << "int* const: " << std::is_const<int* const>::value << endl;
  3. 判断两个类型之间的关系tratis

    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
    //is_same
    cout << std::is_same<int,int>::value << endl;
    cout << std::is_same<int,unsigned int>::value << endl;
    cout << std::is_same<int,signed int>::value << endl;

    //is_base_of
    class A{};
    class B:public A{};
    class C{};
    int main()
    {
    cout << std::is_base_of<A,B>::value << endl;
    cout << std::is_base_of<B,A>::value << endl;
    cout << std::is_base_of<C,B>::value << endl;
    }

    //is_convertible
    class A{};
    class B:public A{};
    class C{};

    int main()
    {
    bool b2a = std::is_convertible<B*,A*>::value;
    bool a2b = std::is_convertible<A*,B*>::value;
    bool b2c = std::is_convertible<B*,C*>::value;
    cout << b2a << endl;
    cout << a2b << endl;
    cout << b2c << endl;
    }
  4. 类型转换的traits

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #include <iostream>
    #include <type_traits>
    #include <memory>
    using std::cin;
    using std::cout;
    using std::endl;
    using std::is_same;

    //移除和添加的示例
    template <class T>
    struct Construct
    {
    typedef typename std::remove_reference<T>::type U;
    Construct():m_ptr(new U)
    {
    *m_ptr = 999;
    }

    typename std::add_lvalue_reference<U>::type
    Get()const
    {
    return *m_ptr.get();
    }


    private:
    std::unique_ptr<U> m_ptr;
    };


    int main()
    {
    cout << std::boolalpha;
    //添加和移除 const 和 reference
    cout << is_same<const int,std::add_const<int>::type>::value << endl;
    cout << is_same<int,std::remove_const<const int>::type>::value << endl;
    cout << is_same<int&,std::add_lvalue_reference<int>::type>::value << endl;
    cout << is_same<int&&,std::add_rvalue_reference<int>::type>::value << endl;
    cout << is_same<int,std::remove_reference<int&>::type>::value << endl;
    cout << is_same<int,std::remove_reference<int&&>::type>::value << endl;

    //移除数组的顶层维度
    cout << is_same<int,std::remove_extent<int[]>::type>::value << endl;
    cout << is_same<int[2],std::remove_extent<int[][2]>::type>::value << endl;
    cout << is_same<int[2][3],std::remove_extent<int[][2][3]>::type>::value << endl;

    //移除所有维度
    cout << is_same<int,std::remove_all_extents<int[][2][3]>::type>::value << endl;

    //取公共部分
    typedef std::common_type<unsigned char,short,int>::type NumericType;
    cout << is_same<int,NumericType>::value << endl;

    //移除和添加的示例
    Construct<int> c;
    int a = c.Get();
    cout << a++ << endl;
    cout << a << endl;


    return 0;
    }
  5. std::decay 对于上述有时候需要移除或添加的可能会比较麻烦,如下

    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
    template <typename T>
    typename std::remove_cv<typename std::remove_reference<T>::type>::type * creat_1()
    {
    return new typename std::remove_cv<typename std::remove_reference<T>::type>::type();
    }

    int * p = creat_1<const int&>();

    //引入std::decay
    template <typename T>
    typename std::decay<T>::type * creat_2()
    {
    return new typename std::decay<T>::type();
    }
    int * x = creat_2<const int*>();

    //decay的转换规则: a.对于普通类型是移除引用和cv符 b.还可以用于函数和数组 , 具体如下
    //1.先移除T类型引用,得到类型U,U定义为remove_reference<T>::type
    //2.如果is_array<U>::value为true,修改类型为 remove_extent::type*
    //3.否则,如果is_function<U>::value为true,修改类型为add_pointer<U>::type
    //4.否则,修改类型为remove_cv<U>::type
    typedef std::decay<int>::type A; //int
    typedef std::decay<int&>::type B; //int
    typedef std::decay<int&&>::type C; //int
    typedef std::decay<const int&>::type D; //int
    typedef std::decay<int[2]>::type E; //int*
    typedef std::decay<int(int)>::type F; //int(*)int
  6. decay用法的一个例子,保存函数

    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
    void OutPut()
    {
    cout << "hello,world" << endl;
    }

    template <typename F>
    struct Save_Function
    {
    using FnType = typename std::decay<F>::type;

    explicit Save_Function(F & f):m_fn(f)
    {

    }

    void Run()
    {
    m_fn();
    }

    FnType m_fn;
    };
    int main()
    {
    void(*func)() = OutPut;
    Save_Function<void(*)()> l(func);
    l.Run();
    }
2.根据条件选择的tratis

简单好用,直接看例子就行了

1
2
3
4
5
6
7
8
//原型
template <bool B,class T,class F>
struct conditional;

typedef std::conditional<true,int,float>::type A; // int
typedef std::conditional<false,int,float>::type B; //float
typedef std::conditional<std::is_integral<A>::value,long,int>::type C; //long
typedef std::conditional<std::is_integral<B>::value,long,int>::type D; //int
3.获取可调用对象返回类型的tratis

很容易想到的就是auto 和 decltype结合使用,但有局限性

1
2
3
4
5
6
7
8
9
10
class A
{
A()=delete; //如果构造delete,则不能用decltype
public:
int operator()(int i)
{
return i;
}
};
decltype(A()(0)) i = 4; //编译报错,因为A没有构造

对此可以用到std::declval (能获取任何类型的临时值)

1
2
3
decltype(std::declval<A>()(std::declval<int>())) i = 4;
//看起来 std::declval<A>()这段获取一个A的临时值 然后调用operator()
// std::declval<int>()获取一个int的临时值

另一种方案就是std::result_of

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
//std::result_of 原型
template <class F,class... ArgTypes>
class result_of<F(ArgTypes...)>;

//简单用法
int fn(int){return int();}
typedef int(&fn_ref)(int);
typedef int(*fn_ptr)(int);
struct fn_class { int operator()(int i){return i;}};

int main()
{
typedef std::result_of<decltype(fn)&(int)>::type A;
//这里应该是创建了一个fn函数的引用,函数确定类型有点怪
typedef std::result_of<fn_ref(int)>::type B;
typedef std::result_of<fn_ptr(int)>::type C;
typedef std::result_of<fn_class(int)>::type D;

cout << std::boolalpha;
cout << "typedefs of int: " << endl;

cout << "A: " << std::is_same<int,A>::value << endl;
cout << "B: " << std::is_same<int,B>::value << endl;
cout << "C: " << std::is_same<int,C>::value << endl;
cout << "D: " << std::is_same<int,D>::value << endl;
}

通过auto 和 decltype已经简化了一次返回值的确定,这个能在某些情况下更近一步简化,如下

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
template <typename Fn>
auto GroupBy(const vector<Person>& vt,const Fn& keySlector)
->multimap<decltype(keySlector((Person&)nullptr),Person)>
{
//推断keySlector的返回值类型
typedef decltype (keySlector(*(Person*)nullptr)) key_type;
multimap<key_type,Person> map;
std::for_each(vt.begin(),vt.end(), [&](const Person& person)
{
map.insert(make_pair(keySlector(person),person));
});
return map;
}
//使用std::result_of
template <typename Fn>
multimap<typename std::result_of<Fn(Person)>::type,Person>
GroupBy(const vector<Person>& vt,const Fn& keySlector)
{
//推断keySlector的返回值类型
typedef std::result_of<Fn(Person)>::type key_type;
multimap<key_type,Person> map;
std::for_each(vt.begin(),vt.end(), [&](const Person& person)
{
map.insert(make_pair(keySlector(person),person));
});
return map;
}
4.根据条件禁用或启用某种或某些类型tratis

一个规则SFINAE(substitution-failure-is-not-an-error),替换失败并非失败,体现在重载时匹配函数时,匹配到错误的函数不会直接报错,而是继续匹配其它重载

std::enable_if,根据条件来选择重载函数

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//std::enable_if的原型
template <bool B,class T = void>
struct enable_if;

//-----分割线-------
#include <iostream>
#include <type_traits>

using std::cout;
using std::endl;
using std::cin;

//基本用法
template <class T>
typename std::enable_if<std::is_arithmetic<T>::value,T>::type foo(T t)
{
//此例子将限定作用于返回值
return t;
}

//对入参类型做限定 此例中第二参数为整型
template <class T>
T foo2(T t,typename std::enable_if<std::is_integral<T>::value,int>::type =0)
{
return t;
}
//自动推导失败的原因,第二个参数为外敷模板
template <class T,class U>
T foo22(T t,U u)
{
return t;
}

//对模板参数T做限定,T只能为整型
template <class T,class = typename std::enable_if<std::is_integral<T>::value>::type>
T foo3(T t)
{
return t;
}

//模板特化时,对模板参数做限定,只能为浮点型
template <class T,class Enable = void>
class A;

template <class T>
class A<T,typename std::enable_if<std::is_floating_point<T>::value>::type> {};


//一个根据不同入参分类的例子
template <class T>
typename std::enable_if<std::is_arithmetic<T>::value,int>::type foo1(T t)
{
cout << t << endl;
return 0;
}

template <class T>
typename std::enable_if<!std::is_arithmetic<T>::value,int>::type foo1(T& t)
{
cout << typeid(T).name() << endl;
return 1;
}

int main()
{
//基本用法
auto r = foo(1);
auto r1 = foo(1.2);
//auto r2 = foo("hello"); is_arithmetic 已经限定为整形和浮点型

//对入参类型做限定 此例中第二参数为整型
foo2(1,2);
//foo2(1,"hello"); 第二个参数不为整型

//对模板参数T做限定,T只能为整型
foo3(1);
//foo3(1.2); 限定T只能为整型

//模板特化时,对模板参数做限定,只能为浮点型
A<double> a;
//A<int> b;


//一个根据不同入参分类的例子
foo1(123);
foo1("hello");

return 0;
}

再看看一个不用std::enable_if和用的例子,可以看出enable_if能实现强大的重载

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
//不用
template <typename T>
string ToString(T t)
{
if (typeid(T) == typeid(int) || typeid(T) == typeid(double)
typeid(T) == typeid(float) || typeid(T) == typeid(float))
{
std::stringstream ss;
ss << value;
return ss.str();
}
else if (typeid(T) == typeid(string))
{
return t;
}
}
//使用
template <class T>
typename std::enable_if<std::is_arithmetic<T>::value,string>::type
ToString(T& t) { return std::to_string(t);}

template <class T>
typename std::enable_if<!std::is_same<T,string>::value,string>::type
ToString(T& t) { return t;}
//可以看出不仅更简洁,而且代码更优雅
二.可变参数模板
1.可变参数模板函数
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <iostream>
#include <tuple>
#include <initializer_list>

using std::cout;
using std::endl;
using std::cin;

//简单的定义
template <class... T>
void f(T... args)
{
//打印参数包参数个数
cout << sizeof...(args) << endl;
}

//1.模板函数递归展开

//终止条件可以设置 0个时终止 或 任意个数终止
void print1()
{
cout << "empty" << endl;
}

//template <class T>
//void print1(T t)
//{
// c_out << "parameter " << t << e_ndl;
//}
template <class T,class... Args>
void print1(T head,Args... args)
{
cout << "parameter " << head << endl;
print1(args...);
}

//type_traits 展开
template<std::size_t I = 0, typename Tuple>
typename std::enable_if<I == std::tuple_size<Tuple>::value>::type print_tp(Tuple t)
{
}

template<std::size_t I = 0, typename Tuple>
typename std::enable_if<I < std::tuple_size<Tuple>::value>::type print_tp(Tuple t)
{
std::cout << std::get<I>(t) << std::endl;
print_tp<I + 1>(t);
}

template<typename... Args>
void print2(Args... args) {
print_tp(std::make_tuple(args...));
}

//2.逗号表达式和初始化列表展开参数包
template <class T>
void print3(T t)
{
cout << t << endl;
}
template <class... Args>
void print3(Args... args)
{
int arr[] ={(print3(args),0)...};
}

//改进,不定义数组,通过std::initializer_list 且结合lambad表达式省略终止模板函数的声明定义
template <typename... Args>
void expand(Args... args)
{
std::initializer_list<int>{([&]{cout << args << endl;}(),0)...};
}

int main()
{
//简单的定义
cout << "-----size--------" << endl;
f(1,2,3,4,5,6);

//模板函数递归展开
cout << "-----print1------" << endl;
print1(1,2,3,4,5,6,7,"hello");

//type_traits 展开
cout << "-----print2------" << endl;
print2(1,2,3,4,5,6);

//逗号表达式展开参数包
cout << "-----print3------" << endl;
print3(1,2,3,4,5,"cow");

//初始化列表展开参数包
cout << "-----expand------" << endl;
expand(1,2,3);

return 0;
}

注意type_traits展开时, std::enable_if 的第二参数可以省略,省略的话默认为void

2.可变参数模板类

模板递归和特化的方式展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename... Args> struct sum;         //前向声明

template <typename First,typename... Rest>
struct sum<First,Rest...> //开始展开
{
enum {value = sum<First>::value + sum<Rest...>::value};
};

template <typename Last> struct sum<Last> //特化
{
enum {value = sizeof(Last)};
};

template <> struct sum <> //参数包为0时的特化
{
enum {value = 0};
};

使用std::integral_constant替换枚举定义

1
2
3
4
5
6
7
8
9
10
11
template <typename... Args> struct sum3;

template <typename First,typename... Rest>
struct sum3<First,Rest...>:std::integral_constant<int,sum3<First>::value + sum3<Rest...>::value>
{
};

template <typename Last>
struct sum3<Last>:std::integral_constant<int,sizeof(Last)>
{
};

继承的方式展开参数包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//整形序列定义
template <int...>
struct IndexSeq{};

//继承方式,开始展开参数包
template <int N,int... Indexes>
struct MakeIndexes : MakeIndexes<N-1,N-1,Indexes...> {};
//struct MakeIndexes : MakeIndexes<N-1,Indexes...,N-1> {}; //反序展开

//模板特化,终止展开参数包
template <int... Indexes>
struct MakeIndexes<0,Indexes...>
{
typedef IndexSeq<Indexes...> type;
};

通过using实现

1
2
3
4
5
6
7
8
template <int N,int... Indexes>
struct make_index : make_index<N-1,N-1,Indexes...> {};

template <int... Indexes>
struct make_index<0,Indexes...>
{
using type = IndexSeq<Indexes...>;
};

汇总

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <iostream>
using std::cin;
using std::cout;
using std::endl;


//第一章中介绍的tuple就是这样的一个例子 它的定义如下
//template <class... Types>
//class tuple;

//1.模板递归和特化方式展开参数包
template <typename... Args> struct sum;

template <typename First,typename... Rest>
struct sum<First,Rest...>
{
enum {value = sum<First>::value + sum<Rest...>::value};
};

template <typename Last> struct sum<Last>
{
enum {value = sizeof(Last)};
};

template <> struct sum <>
{
enum {value = 0};
};

//另一种改变前向声明的 和 上面的区别:这个最少有一个参数,参数包中不能没有参数
template <typename First,typename... Args> struct sum2;

template <typename First,typename... Rest>
struct sum2
{
enum {value = sum<First>::value + sum<Rest...>::value};
};

template <typename Last> struct sum2<Last>
{
enum {value = sizeof(Last)};
};

//使用 std::integral_constant 替换 定义枚举
template <typename... Args> struct sum3;

template <typename First,typename... Rest>
struct sum3<First,Rest...>:std::integral_constant<int,sum3<First>::value + sum3<Rest...>::value>
{
};

template <typename Last>
struct sum3<Last>:std::integral_constant<int,sizeof(Last)>
{
};

//继承方式展开参数包 这个属实有点魔法
//整形序列定义
template <int...>
struct IndexSeq{};

//继承方式,开始展开参数包
template <int N,int... Indexes>
struct MakeIndexes : MakeIndexes<N-1,N-1,Indexes...> {};
//struct MakeIndexes : MakeIndexes<N-1,Indexes...,N-1> {}; //反序展开

//模板特化,终止展开参数包
template <int... Indexes>
struct MakeIndexes<0,Indexes...>
{
typedef IndexSeq<Indexes...> type;
};


//通过using 实现
template <int N,int... Indexes>
struct make_index : make_index<N-1,N-1,Indexes...> {};

template <int... Indexes>
struct make_index<0,Indexes...>
{
using type = IndexSeq<Indexes...>;
};



int main()
{
//模板递归和特化方式展开参数包
cout << "-----sum-----" << endl;
cout << sum<char,int,double>::value << endl;

//另一种改变前向声明的
cout << "-----sum2----" << endl;
cout << sum2<char,int,double>::value << endl;

//用 std::integral_constant 替换 枚举定义
cout << "-----sum3----" << endl;
cout << sum3<int,double,char>::value << endl;

//继承方式展开参数包 这个属实有点魔法
cout << "-----class---" << endl;
using T = MakeIndexes<3>::type;
cout << typeid(T).name() << endl;

//通过using 实现
cout << "-----using---" << endl;
using H = make_index<3>::type;
cout << typeid(H).name() << endl;

return 0;
}

3.可变参数模板消除重复代码

两个示例

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

//用以消除重复代码
//示例1
template <typename T>
void Print(T t)
{
cout <<t << endl;
}
template <typename T,typename... Args>
void Print(T t,Args... args)
{
cout << t;
Print(args...);
}

//示例2
struct A
{
int a;
A(int x){a=x;}
};

struct B
{
int x, y;
B(int xx,int yy){x=xx,y=yy;}
};

template <typename T,typename... Args>
T* Instance1(Args... args)
{
return new T (args...);
}

//示例3 采用完美转发
template <typename T,typename... Args>
T* Instance2(Args&&... args)
{
//保持参数种的右值引用
return new T (std::forward<Args>(args)...);
}

int main()
{
cout << "----1----" << endl;
Print(1,2,3,4,2,5);

cout << "----2----" << endl;
A* pa = Instance1<A>(1);
B* pb = Instance1<B>(1,2);
cout << pa->a << endl;
cout << pb->x << pb->y << endl;

cout << "----3----" << endl;
A* fpa = Instance2<A>(1);
B* fpb = Instance2<B>(1,2);
cout << fpa->a << endl;
cout << fpb->x << fpb->y << endl;
return 0;
}

三.可变参数模板和type_traits的综合应用
1.optional的实现

optional 储存一个T类型的值,只有他被初始化后才是有效的,实现了未初始化的概念.

思路:为了实现这个东西,他要储存一个T类型的值,且不一定要初始化,所以它应该存在一个缓冲区,用以储存这个值,一般会想到采用 char xx[32]; 和 new (xx) MyStruct ,准备一个char数组和placement new 来实现,但一个问题是,齐位问题,可能导致效率问题或出错,对于这个问题可以采用 std::aligned_storage 解决

1
2
3
4
5
6
7
8
//std::aligned_storage 原型如下
template <std::size_t Len, std::size_t Align =/*default-alignment*/>
struct aligned_storage;

//两种用法 1
std::aligned_storage<sizeof(T),std::alignment_of<T>::value>
//2
std::aligned_storage<sizeof(T),alignof(A)>

这块地方看到另一种placement的用法,之前只会 int *p = new (xx) MyStruct; 这种用法

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#include <iostream>
#include <type_traits>
#include <string>
using std::string;


//placement new 和 aligned_storage 结合使用的例子
namespace example1
{
struct A
{
int avg;
A (int a,int b):avg((a+b)/2) {}
};
typedef std::aligned_storage<sizeof(A), alignof(A)>::type Aligned_A;

void test()
{
//placement new 和 aligned_storage 结合使用的例子
Aligned_A a,b;
new (&a) A(10,20);
b=a;
//三种不同的方式
//将b 强转为 A& 的引用
std::cout << reinterpret_cast<A&>(a).avg << std::endl;
//
std::cout << reinterpret_cast<A*>(&a)->avg << std::endl;
//用 指针也行
std::cout << (*(((A*)(&a)))).avg << std::endl;
}
}

template <typename T>
class optional
{
//定义内存对齐的缓冲区类型
using data_t = typename std::aligned_storage<sizeof(T), alignof(T)>::type;

public:
//构造,析构
optional() {}

optional(const T& v)
{
//内部通过placement new 创建对象
Create(v);
}

optional(const optional& other)
{
if (other.IsInit())
Assign(other);
}

~optional()
{
Destroy();
}

//根据参数创建
template <class... Args>
void Emplace(Args&&... args)
{
Destroy();
Create(std::forward<Args>(args)...);
}

bool IsInit()const {return m_hasInit;}

explicit operator bool()const
{
return IsInit();
}

//取出对象,毕竟optional 储存了一个值,相当于给这个值加了一层保险,我理解的optional的用途
const T& operator*()const
{
if (IsInit())
{
return *((T*)(&m_data));
}
throw std::logic_error("is not init");
}

private:
template <class... Args>
void Create(Args&&... args)
{
//关于使用完美转发的理解,保持右值引用,避免复制
new (&m_data) T(std::forward<Args>(args)...);
m_hasInit = true;
}

//销毁缓冲区对象
void Destroy()
{
if (m_hasInit)
{
m_hasInit = false;
((T*)(&m_data))->~T();
}
}

void Assign(const optional& other)
{
if (other.IsInit())
{
Copy(other.m_data);
m_hasInit = true;
}
else
{
Destroy();
}
}

void Copy(const data_t& val)
{
Destroy();
new (&m_data) T(*((T*)(&val)));
}


private:
bool m_hasInit = false; //是否初始化
data_t m_data; //缓冲区
};

struct MyStruct
{
MyStruct(){}
MyStruct(int a,int b):m_a(a),m_b(b){}
int m_a;
int m_b;
friend std::ostream& operator<<(std::ostream& os,const MyStruct& temp);
};
std::ostream& operator<<(std::ostream& os,const MyStruct& temp)
{
os << " m_a:" << temp.m_a << " m_b:" << temp.m_b;
return os;
}

void test()
{
optional<string> a("ok");
optional<string> b("ok");
optional<string> c("aa");
c = a;

optional<MyStruct> op;
op.Emplace(1,2);
MyStruct t;
if (op)
t = *op;
op.Emplace(3,4);
t = *op;

std::cout << *a << *op << t << std::endl;

//未初始化时抛出异常
optional<int> xx;
std::cout << *xx;
}

int main()
{
test();
return 0;
}

2.惰性求值类lazy的实现

C++没有的东西,不过可以自己实现,应用场景举个例子,初始化对象时,对象很大,创建需要较长时间,可以并不需要立马获取大量数据,在使用时获取

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
//c#中的一个使用例子
class LargeObject
{
public int InitializedBy { get {return intiBy;} }
int initBy = 0;
public LargeObject(int initializedBy)
{
initBy = initializedBy;
Console.WriteLine("LargeObject was created on thread id {0}.",initBy);
}
public long[] Data = new long[1000000];
}

class TestLazy
{
Lazy<LargeObject> lazyLargeObject = null;

public TestLazy()
{
//创建一个延迟加载对象
lazyLargeObject = new Lazy<LargeObject>(InitLargeObject);
}

public void ReallyLoad()
{
//此时加载对象
lazyLargeObject.Value;
Console.WritenLine("Lazy load big object");

//do something
}
}

void test()
{
TestLazy t = new TestLazy();
t.ReallyLoad(); //此时真正加载
}

整体思路比较简单,利用lambda的特性将传入的函数和值一起保存起来(将值捕获),真正加载的时候再调用函数,有一点值得注意,Lazy类的构造中,书中是左值引用,但辅助函数中为了做到提高效率采用完美转发,但传入的是右值引用,构造中的左值引用不能接受右值引用,会报错

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <iostream>
#include <functional>
#include <memory>

namespace op
{
template <typename T>
class optional
{
//定义内存对齐的缓冲区类型
using data_t = typename std::aligned_storage<sizeof(T), alignof(T)>::type;

public:
//构造,析构
optional() {}

optional(const T& v)
{
//内部通过placement new 创建对象
Create(v);
}

optional(const optional& other)
{
if (other.IsInit())
Assign(other);
}

~optional()
{
Destroy();
}

//根据参数创建
template <class... Args>
void Emplace(Args&&... args)
{
Destroy();
Create(std::forward<Args>(args)...);
}

bool IsInit()const {return m_hasInit;}

explicit operator bool()const
{
return IsInit();
}

//取出对象,毕竟optional 储存了一个值,相当于给这个值加了一层保险,我理解的optional的用途
T& operator*()const
{
if (IsInit())
{
return *((T*)(&m_data));
}
throw std::logic_error("is not init");
}

private:
template <class... Args>
void Create(Args&&... args)
{
//关于使用完美转发的理解,保持右值引用,避免复制
new (&m_data) T(std::forward<Args>(args)...);
m_hasInit = true;
}

//销毁缓冲区对象
void Destroy()
{
if (m_hasInit)
{
m_hasInit = false;
((T*)(&m_data))->~T();
}
}

void Assign(const optional& other)
{
if (other.IsInit())
{
Copy(other.m_data);
m_hasInit = true;
}
else
{
Destroy();
}
}

void Copy(const data_t& val)
{
Destroy();
new (&m_data) T(*((T*)(&val)));
}


private:
bool m_hasInit = false; //是否初始化
data_t m_data; //缓冲区
};
}

using namespace op;

template <typename T>
struct Lazy
{
Lazy() {}

template <typename Func,typename... Args>
Lazy(Func&& f,Args&&... args) //书上这里为左值引用,但貌似左值引用不能接受右值引用,然后会报错,完美转发就不行
{
//定义一个lambda并保存起来
m_func = [&f,&args...]{return f(args...);};
}

//延迟计算,可以看到保存的函数,在这里使用,所以此处才是真正加载的地方,
// 且下次不用再计算了,用optional的特性,判断值是否初始化
T& Value()
{
if (!m_value.IsInit())
{
//并将值保存起来
m_value = m_func();
}
return *m_value;
}

bool IsValueCreated()const
{
return m_value.IsInit();
}

private:
std::function<T()> m_func;
optional<T> m_value;
};

//辅助函数
template <class Func,typename... Args>
Lazy<typename std::result_of<Func(Args...)>::type>
lazy(Func&& fun,Args&&... args)
{
return Lazy<typename std::result_of<Func(Args...)>::type>(std::forward<Func>(fun),std::forward<Args>(args)...);
}

struct BigObject
{
BigObject()
{
std::cout << "lazy load big object " << std::endl;
}
};

struct MyStruct
{
MyStruct()
{
m_obj = lazy([]{return std::make_shared<BigObject>();});
}

void load()
{
m_obj.Value();
}

Lazy<std::shared_ptr<BigObject>> m_obj;
};

int Foo(int x)
{
return x * 2;
}

void test2()
{
int y = 4;
auto lazyer1 = lazy(Foo,y);
std::cout << lazyer1.Value() << std::endl;

Lazy<int> lazyer2 = lazy([]{return 12;});
std::cout << lazyer2.Value() << std::endl;

std::function<int(int)> f = [](int x){return x+3;};
auto lazyer3 = lazy(f,3);
std::cout << lazyer3.Value() << std::endl;

MyStruct t;
t.load();
}

int main()
{
test2();
return 0;
}
3.dll帮助类

不是太清楚dll是啥,对这里dll的理解,貌似就是一个函数库,放了很多函数,来看点例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void TestDll()
{
typedef int(*pMax)(int a,int b);
typedef int(*pGet)(int a);
HINSTANCE hDll = LoadLibrary("MyDll.dll");
if (hMode == nullptr)
return;

pMax Max = (pMax)GetProAddress(hDll,"Max");
if (Max == nullptr)
return;

int ret = Max(5,8); // ret = 8

pGet Get = (pGet)GetProAddress(hDll,"Get");
if (Get == nullptr)
return;

int rea = Get(5); // ret = 5

FreeLibrary(hDll);
}

虽然不是很清楚dll是啥,但大概是干啥是知道的,通过dll根据函数名称,获取函数的地址,然后传给对应定义的指针,很显然,上述方法很麻烦,不仅要根据不同的函数,先定义不同的函数指针类型,还要获取之后再调用,步骤繁琐,对此本章的目的就是凭借可变参数模板和 type_traits 解决上述问题

对此我们希望将定义 获取函数 计算结果 整合为一个整体,类似如下

1
2
3
//ret 为 结果 , 第一个参数为函数名, 第二个参数为函数的入参
//很容易想到,对于入参,可能不止一个参数,这就可以用到函数包
auto ret = CallDllFunc(const string& funName, T arg);

思路: 封装成 getFunction 和 excuteFunction ,

  1. 函数的返回值可能是不同类型,如何以一种通用的返回值来消除这个不同返回值导致的差异呢?
  2. 函数的入参数目可能任意数目,且类型也不相同,如何来消除入参个数和类型的差异呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//对于第一步getFunciton的封装
template <typename T>
std::function<T> GetFunction(const string& funcName)
{
FARPROC funAddress = GetProAddress(m_hMod,funcName.c_str());
return std::function<T>((T*)(funAddress));
}

//调用GetFunction的例子
auto pMax = GetFunction<int(int,int)>("Max");
auto pGet = GetFunction<int(int)>("Get");

//第二步excuteFuncion的封装,对于返回值的处理可以采用 type_trais中的result_of
template <typename T,typename... Args>
typename std::result_of<std::functon<T>(Args...)>::type
ExecuteFunc(const string& funcName,Args&&... args)
{
return GetFunction<T>(funcName)(args...);
}

//调用的例子
auto max = ExecuteFunc<int(int,int)>("Max",5,8);
auto get = ExecuteFunc<int(int)>("Get",5);
//一步到位嗷

来点完整代码实现

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <windows.h>
#include <string>
#include <map>
#include <functional>
using namespace std;

class DllParser
{
public:
DllParser():m_hMod(nullptr)
{
}

~DllParser()
{
UnLoad();
}

bool Load(const string& dllPath)
{
m_hMod = LoadLibraryA(dllPath.data());
if (nullptr == m_hMod)
{
printf("LoadLibrary failed\n");
return false;
}
}

bool UnLoad()
{
if (m_hMod == nullptr)
return true;

auto b = FreeLibrary(m_hMod);
if (!b)
return false;

m_hMod = nullptr;
return true;
}

template <typename T>
std::function<T> GetFunction(const string& funcName)
{
auto it = m_map.find(funcName);
if (it == m_map.end())
{
auto addr = GetProcAddress(m_hMod,funcName.c_str());
if (!addr)
return nullptr;
m_map.insert(std::make_pair(funcName,addr));
it = m_map.find(funcName);
}
return std::function<T>((T*)(it->second));
}

template <typename T,typename... Args>
typename result_of<function<T>(Args...)>::type ExecuteFunc(const string& funcName,Args&&... args)
{
auto f = GetFunction<T>(funcName);
if (f == nullptr)
{
string s = "can not find this function " + funcName;
throw s;
}
return f(forward<Args>(args)...);
}

//对完美转发的一点思考: 完美转发应该是根据模板中的来确定的,也就是 Args 的类型
//但这里的Args 又是个 universal-reference T&& 由类型推导获得

private:
HMODULE m_hMod;
map<string,FARPROC> m_map;
};
4.lambda链式调用

首先是PLL的一个例子,和DLL一样不太清楚PLL是啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main()
{
auto t = creat_task([]()->int
{
return 0;
});

//create a lambda that increments its input value
auto increment = [](int n) {return n+1;}

//run a chain of continuations and print the result
int result = t.then(increment).then(increment).then(increment).get();

wcout << result << endl;

/*OutPut
3
*/
}

很明显能看出链式的意义

下面就是具体怎么实现

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <functional>
#include <type_traits>
using namespace std;

template <typename T>
class Task;

template <typename R,typename... Args>
class Task<R(Args...)>
{
public:
Task(function<R(Args...)>&& f):m_fn(move(f)){}
Task(function<R(Args...)>& f):m_fn(f){}

R Run(Args&&... args)
{
return m_fn(forward<Args>(args)...);
}

template<typename F>
auto Then(F&& f)->Task<typename result_of<F(R)>::type(Args...)>
{
using return_type = typename result_of<F(R)>::type;

auto func = move(m_fn);

return Task<return_type(Args...)>([func,f](Args&&... args)
{
return f(func(forward<Args>(args)...));
});
}

private:
function<R(Args...)> m_fn;
};

void TestTask()
{
Task<int(int)> task([](int i){return i;});

auto result = task.Then([](int i){return i+1;}).
Then([](int i){return i+2;}).Then([](int i){return i+3;})
.Run(1);

cout << result << endl;
}

int main() {
TestTask();
return 0;
}

//属于是 知道实现了什么 ,但代码还没看懂
5.any类的实现

应用部分先停一下,先往后看,以后再来回顾

四.总结

C++11进一步增强了C++泛型编程的能力, type_traits 使我们可以在编译期获取对类型进行查询,计算,判断,转换和选择,可以在编译期检测到输入参数类型的正确性,还能实现更为强大的重载。可变参数模板, 大大减少重复的模板定义,增强了模板的功能,可以写出更泛化的代码

第四章 使用C++11解决内存泄漏的问题

一.shared_ptr 共享的智能指针

该智能指针使用引用计数,每一个shared_ptr的拷贝都指向相同的内存,在最后一个shared_ptr析构的时候内存,才会被释放,这样就不会导致重复析构同一块内存

1.shared_ptr 的基本用法

1.初始化

初始化的各种方式,通过构造函数,std::make_shared辅助函数和reset方法来初始化.

1
2
3
4
5
6
7
8
9
10
//智能指针的初始化
std::shared_ptr<int> p (new int(1));
std::shared_ptr<int> p2 = p;
std::shared_ptr<int> ptr;
ptr.reset(new int(1));
if (ptr)
{
std::cout << "ptr is not null !" << std::endl;
}
//应当优先使用make_shared 因为它更高效

对于一个未初始化的智能指针,可以通过reset方法来初始化,当智能指针中有值时,调用reset会使引用计数减1

2.获取原始指针

1
2
std::shared_ptr<int> ptr(new int(1));
int* m = ptr.get();

3.指定删除器

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
//指定删除器
void DeleteIntPtr(int *p)
{
delete p;
}

template <typename T>
std::shared_ptr<T> make_shared_array(size_t size)
{
return std::shared_ptr<T>(new T[size],std::default_delete<T[]>());
}

int main()
{
//指定普通函数为删除器
std::shared_ptr<int> p1 (new int, DeleteIntPtr);
//指定lambda表达式为删除器
std::shared_ptr<int> p2 (new int[10],[](int* p){delete []p;});
//指定标准库中的
std::shared_ptr<int> p3 (new int [10],std::default_delete<int[]>());
//封装一个支持数组的方法
std::shared_ptr<int> p4 = make_shared_array<int>(10);
std::shared_ptr<char> p = make_shared_array<char>(10);

return 0;
}
2.使用shared_ptr 需要注意的问题
  1. 不要用一个原始指针初始化多个shared_ptr

    1
    2
    3
    4
    //此时引用计数会出现问题,导致重复析构同一块内存
    int * ptr = new int;
    std::shared_ptr<int> p1(ptr);
    std::shared_ptr<int> p2(ptr);
  2. 不要在函数实参中创建shared_ptr

    1
    2
    3
    4
    5
    function (shared_ptr<int>(new int),g());
    //原因:C++实参的计算顺序在不同的编译器中不同的调用约定下可能是不一样的,如果先 new int 然后调用g(),
    //此时如果g()发生异常,int内存就泄漏了,可以采用如下方式解决
    shared_ptr<int> p(new int);
    f(p,g());
  3. 返回this指针,可能导致重复析构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //不要将this指针作为shared_ptr返回出来,this本质是一个裸指针
    struct A
    {
    shared_ptr<A>GetSelf()
    {
    return shared_ptr<A>(this);
    }
    };
    int main()
    {
    shared_ptr<A> sp1(new A);
    shared_ptr<A> sp2 = sp1->GetSelf();
    return 0;
    }

    感觉上应该根第一个问题差不多,都是用一个原始指针创建了两个智能指针,导致引用计数出现问题,重复析构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //正确的做法 让目标类通过派生 std::enable_shared_from_this<T>类.使用基类的方法
    class A:public std::enable_shared_from_this<A>
    {
    std::shared_ptr<A> GetSelf()
    {
    return shared_from_this();
    }
    };
    std::shared_ptr<A> spy(new A);
    std::shared_ptr<A> p = spy->GetSelf();
  4. 循环引用问题

    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
    struct A;
    struct B;

    struct A
    {
    std::shared_ptr<B> bptr;
    ~A() { std::cout << "A is deleted!" << std::endl; }
    };

    struct B
    {
    std::shared_ptr<A> aptr;
    //std::weak_ptr<A> aptr;
    ~B() { std::cout << "B is delted!" << std::endl; }
    };

    void testPtr()
    {
    {
    std::shared_ptr<A> ap(new A);
    std::shared_ptr<B> bp(new B);
    ap->bptr = bp;
    bp->aptr = ap;
    }
    }

    很好理解为什么会出现这种问题,我们知道指向同一内存的智能指针会根据引用计数来析构,此时计数都为2,离开作用域时,引用计数减1,但仍不为0,所以不会调用析构函数

二.unique_ptr 独占的智能指针

与shared_ptr 相反,它不允许其它的智能指针共享其内部的指针,不允许通过赋值,将一个unique_ptr赋值给另一个

1
2
unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = myPtr; //错误,不能复制

但是允许移动

1
2
3
unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = std::move(myPtr);
unique_ptr<T> ptr = myPtr; //错误,只能移动,不能复制

C++11中没有提供make_unique方法,对此可以自己实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//支持普通指针
template <class T,class... Args> inline
typename std::enable_if<!std::is_array<T>::value,std::unique_ptr<T>>::type
make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

//支持动态数组
template <class T> inline
typename std::enable_if<std::is_array<T>::value && std::extent<T>::value==0,
std::unique_ptr<T>>::type make_unique(size_t size)
{
typedef typename std::remove_extent<T>::type U;
return std::unique_ptr<T>(new U[size]());
}

//过滤掉定长数组
template <class T,class... Args>
typename std::enable_if<std::extent<T>::value!=0,void>::type make_unique(Args&&...) = delete;

对于普通指针,直接创建unique_ptr,对于数组先判断是否为定长数组

1
2
3
//原因
make_unique<T[10]>(10); //不能这样调用
meke_unique<T[]>(10); //这样调用

另一个与shared_ptr的区别,unique_ptr可以指向数组

1
2
std::unique_ptr<int[]> ptr(new int[10]);
ptr[9] = 9;

而std::shared_ptr<int[]> prt(new int[10]);是不合法的. C++17后应该支持了,所以有可能也不会报错

指定删除器时的差别

1
2
3
4
5
6
7
8
std::shared_ptr<int> ptr(new int(1), [](int* p){ delete p;});
std::unique_ptr<int,void(*)(int*)> prt(new int(1), [](int* p){ delete p;});
//unique_ptr需要指明删除器的类型,不能像shared_ptr这样直接指定删除器
//另一个细节时,上述lambda没有捕获的时候是正确的
std::unique_ptr<int,void(*)(int*)> pt(new int(1), [&](int* p){ delete p;});
//原因:没有捕获变量的lambda可以直接转换为函数指针
//可以用包装器解决这个问题
std::unique_ptr<int,std::function<void(int*)>> pt(new int(1), [&](int* p){ delete p;});

或者这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <memory>
#include <functional>

struct MyDeleter
{
void operator()(int* p)
{
std::cout << "delete" << std::endl;
delete p;
}
};
int main()
{
std::unique_ptr <int, MyDeleter> p (new int(1));
return 0;
}
三. weak_ptr 弱引用的智能指针

主要用来协助shared_ptr ,用来监视shared_ptr , 不会使引用计数+1

1.weak_ptr 的基本用法
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
41
42
#include <iostream>
#include <memory>

//3.lock 获取监视的shared_ptr
std::weak_ptr<int> b3;
void f()
{
if (b3.expired())
{
std::cout << "b3 is expired " << std::endl;
}
else
{
auto ret = b3.lock();
std::cout << *ret << std::endl;
}
}

int main()
{
//1.use_count 观测计数
std::shared_ptr<int> p1(new int(10));
std::weak_ptr<int> b1(p1);
std::cout << b1.use_count() << std::endl;

//2.expired 观测是否已被释放
std::shared_ptr<int> p2(new int(10));
std::weak_ptr<int> b2(p2);
if (b2.expired())
std::cout << "already been deleted " << std::endl;
else
std::cout << "not yet been deleted " << std::endl;

//3.lock 获取监视的shared_ptr
{
auto p3 = std::make_shared<int>(42);
b3 = p3;
f();
}

return 0;
}
2.weak_ptr 返回this指针
1
2
3
4
5
6
7
8
9
10
11
12
13
struct A:public std::enable_shared_from_this<A>
{
std::shared_ptr<A> GetSelf()
{
return shared_from_this();
}
~A()
{
std::cout << "A is deleted " << std::endl;
}
};
std::shared_ptr<A> spy(new A);
std::shared_ptr<A> p = spy -> GetSelf();

shared_from_this 通过内部的weak_ptr调用lock方法之后返回智能指针

3.weak_ptr 解决循环引用问题

weak_ptr 只是观测 不会增加引用计数,所以bp的引用计数为1,析构时减为0,执行析构的同时将成员A析构,这样A的引用计数也变为1了,A也顺利执行

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
#include <iostream>
#include <memory>

struct A;
struct B;

struct A
{
std::weak_ptr<B> bptr;
~A() { std::cout << "A is deleted " << std::endl; }
};

struct B
{
std::shared_ptr<A> aptr;
~B() { std::cout << "B is deleted " << std::endl; }
};

void testPtr()
{
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
}

int main()
{
testPtr();
return 0;
}
四. 通过智能指针管理第三方库分配的内存
五. 总结

智能指针是解决内存泄漏问题的利器,但应该注意一些使用上的细节

  1. shared_ptr 和 unique_ptr 如何选择
  2. weak_ptr 和 shared_ptr 的关系, 使用weak_ptr 来解决 shared_ptr 使用中出现的问题,循环引用或是返回this指针

第五章 使用C++11让多线程开发变得简单

第六章 使用C++11中便利的工具

一.处理日期和时间的chrono库

主要是三种类型:时间间隔duration 时钟clocks 和 时间点time point

1.记录时长的duration

记录时间长度的类型

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
//原型
template <class Rep,class Period = std::ratio<1,1> >
class duration;
//Rep是一个数值类型表示时钟的类型, std::ratio 表示时钟周期,它的原型如下
template <std::intmax_t Num,std::intmax_t Denom = 1>
class ratio;
//Num代表分子,Denom代表分母 如 ratio<2> 代表两秒,ratio<60*60*24>代表一天,ratio<1,1000> 1/1000s
//标准库中定义了一些常用的时间间隔如下
typedef duration <Rep,ratio<3600,1> > hours;
typedef duration <Rep,ratio<60,1> > minutes;
typedef duration <Rep,ratio<1,1> > seconds;
typedef duration <Rep,ratio<1,1000> > millisecond;
typedef duration <Rep,ratio<1,1000000> > microseconds;
typedef duration <Rep,ratio<1,1000000000> > nanoseconds;

//常用的用法,线程的休眠
std::this_thread::sleep_for(std::chrono::seconds(3)); //休眠3s
std::this_thread::sleep_for(std::chrono::milliseconds(100)); //休眠100ms

//以及获取周期数的方法 count()
std::chrono::milliseconds ms {3};

std::chrono::microseconds us = 2 * ms;

std::cout << "3 ms duration has " << ms.count() << " ticks\n"
<< "6000 us duration has " << us.count() << " ticks\n";

另一个值得注意的就是,duraion之间的运算,对于不同时钟周期的duration会先统一成一种时钟,再做加减运算,规则如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//对于ratio<x1,y1>count1; ratio<x2,y2>count2; 如果x1,x2的最大公约数为x,y1,y2的最大公倍数为y,
//那么统一之后的ratio为ratio<x,y>
void TestChrono()
{
std::chrono::duration<double,std::ratio<9,7>> d1(3);
std::chrono::duration<double,std::ratio<6,5>> d2(1);

//不同周期的duration会先统一再加减

auto d3 = d1 -d2;

//统一成ratio<3,35>
std::cout << typeid(d3).name() << std::endl;
std::cout << d3.count() << std::endl;
}
2.表示时间点的time point
3.获取时钟的clocks
4.计时器timer

前面2,3我的编译器上跑不了,先忽略了先,这个计时器还是蛮不错的

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <chrono>
#include <iostream>
using namespcae std;
using namespace std::chrono;

class Timer
{
public:
Timer():m_begin(high_resolution_clock::now()){}
void reset() {m_begin = high_resolution_clock::now();}

template <typename Duration = milliseconds >
int64_t elapsed()const
{
return duration_cast<Duration>(high_resolution_clock::now()-m_begin).count();
}

int64_t elapsed_nano()const
{
return elapsed<nanoseconds>();
}

int64_t elapsed_micro()const
{
return elapsed<microseconds>();
}

int64_t elapsed_seconds()const
{
return elapsed<seconds>();
}

int64_t elapsed_minutes()const
{
return elapsed<minutes>();
}

int64_t elapsed_hours()const
{
return elapsed<hours>();
}
private:
time_point<high_resolution_clock> m_begin;
};

void fun()
{
std::cout << "hello,world " << std::endl;
}

int main() {
Timer t;
fun();

std::cout << t.elapsed() << std::endl;
std::cout << t.elapsed_micro() << std::endl;
std::cout << t.elapsed_nano() << std::endl;
std::cout << t.elapsed_seconds() << std::endl;
std::cout << t.elapsed_minutes() << std::endl;
std::cout << t.elapsed_hours() << std::endl;

return 0;
}
二.数值类型和字符串类型的相互转换
三.宽窄字符转换

编译器上跑不了,目前用不到,先放一放

四.总结

利用工具类和函数提高开发的效率

  1. chrono可以方便的获取时间间隔和时间点,配合一些辅助方法还可以输出格式化时间
  2. to_string 和 atoi/atof 等方法可以方便地实现数值和字符串的相互转换
  3. wstring_convert方便地实现宽窄字符转换

第七章 C++11的其他特性

一.委托构造函数和继承构造函数
1.委托构造函数
1
2
3
4
5
6
7
8
//简单来说就是对于构造中重复代码的简化,可以用构造调用另一个构造
class A
{
A(){};
A(int a):A(){};
A(int a,int b):A(a){};
};
//注意调用不要形成一个环就行了
2.继承构造函数
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
//派生类使用基类构造的方法,个人感觉就是通过using 取消了隐藏
struct Base
{
int x;
double y;
string s;

Base(int i):x(i),y(0){}
Base(int i,double j):x(i),y(j){}
Base(itn i,double j,const string& str):x(i),y(j),s(str){}
};
struct Derived : Base
{

};
int main()
{
Derived d(1,2.5,"ok"); //编译错误
}
//对此可以定义构造然后调用基类构造,但是比较繁琐,更好的方法是继承构造
struct Derived : Base
{
using Base::Base; //声明使用基类构造函数
};
//这个特性对同名函数也适用,总之就是using引入了名字,取消隐藏
3.原始的字面量

原始字面量可以直接表示字符串的实际含义,因为有些字符串带特殊字符,比如转义字符,需要特殊处理

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
//直接看例子就理解了  文件路径的例子
#include <iostream>
#include <string>
using namespace std;

int main()
{
string str = "D:\A\B\test.text";
cout << str << endl;

string str1 = "D:\\A\\B\\test.text";
cout << str1 << endl;

string str2 = R"(D:\A\B\test.text)";
cout << str2 << endl;
return 0;
}
//输出结果如下:
D:AB est.text
D:\A\B\test.text
D:\A\B\test.text

//原始字符串字面量的定义是R"xxx(raw string)xxx",xxx为加的字符串会被忽略,但是必须同时出现
#include <string>
#include <iostream>
using namespace std;

int main()
{
//error test没有出现在后面
string str = R"test(D:\A\B\test.text)";
//error 括号前面和后面的字符串不匹配
string str1 = R"test(D:\A\B\test.text)testaa";

string str2 = R"test(D:\A\B\test.text)test"; // ok
cout << str2 << endl; //输出D:\A\B\test.text 忽略test
return 0;
}
4.final 和 override关键字
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
//final 用来限制某个类不能被继承,或者某个虚函数不能被重写
struct A
{
//A::foo is final,限定该虚函数不能被重写
virtual void foo()final;
//ERROR non-virtual function cannot be final
void bar()final;
};
struct B final:A //struct B is final
{
//error : foo cannot be overridden as it's final in A
void foo();
};

struct C : B //error B is final
{};

//override 确保派生类中声明的重写函数与基类的虚函数有相同的签名
struct A
{
virtual void func() {}
};
struct D:A
{
//显式重写
void func()override
{

}
};
二.内存对齐
  1. 内存对齐介绍

    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
    //数据类型存放的内存地址的属性,如一个数据的内存对齐为8时,就是指这个数据的变量的内存地址都是8的倍数.
    //对于为什么需要内存对齐:1.不是所有硬件平台都能随意访问任意位置的内存 2.提高系统的性能

    //在结构体中比较明显
    struct MyStruct
    {
    char a; //1 byte
    int b; //4 bytes
    short c; //2 bytes
    long long d; //8 bytes
    char e; //1 byte
    }
    //msvc中的它的对齐可能就是这样的
    struct MyStruct
    {
    char a; //1 byte
    char pad_0[3];//Padding 3
    int b; //4 bytes
    short c; //2 bytes
    char pad_0[6];//Padding 6
    long long d; //8 bytes
    char e; //1 byte
    char pad_0[7];//Padding 7
    }
    //理想的最佳对齐,是结构体中最大的,起始地址的位置也会影响对齐的偏移方式,可看书中第二个例子
  2. 堆内存的对齐

    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
    //一般讨论内存对齐时,会忽略堆内存,经常会使用malloc分配内存,却不理会这块的对齐方式
    //实际上,malloc一般使用当前平台默认的最大内存对齐数对齐内存,MSVC在32位下一般是8,64是16
    //MSVC下应使用_aligned_malloc来分配特定内存对齐的内存块
    //gcc下一般使用memalign等函数

    //aligned_malloc 的实现 目前应该遇不到,看看就行了
    #include <assert.h>

    inline void* aligned_malloc(size_t size, size_t alignment)
    {
    //检查alignment 是否为 2^N
    assert(!(alignment & (alignment-1)));
    //计算出一个最大的offset的内存
    size_t offset = sizeof(void*) + (--alignment);

    //分配一块带offset的内存
    char* p = static_cast<char*>(malloc(offset + size));
    if (!p) return nullptr;

    //通过"& (~alignment)" 把多计算的offset减掉
    void* r = reinterpret_cast<void*>(reinterpret_cast<size_t>
    (p + offset) & (!alignment));

    //将r当作一个指向void*的指针,在r当前地址前面放入原始地址
    static_cast<void*>(r)[-1] = p;
    //返回经过对齐的内存地址
    return r;
    }

    inline void aligned_free(void* p)
    {
    //返回原始地址,并free
    free(static_cast<void**>(p)[-1]);
    }
  3. 利用alignas指定内存对齐大小

    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
    //如果不希望用默认的内存对齐,可以采用alignas来指定 下面是基本用法

    alignas(32) long long a = 0;

    #define XX 1
    struct alignas(XX) MyStruct_1 {}; //OK

    template <size_t YY = 1>
    struct alignas(YY) MyStruct_2 {}; //OK

    static const unsigned ZZ = 1;
    struct alignas(ZZ) MyStruct_3 {}; //OK

    //只要是编译器数值 就能支持alignas C++11中
    //或者采用如下写法
    _Pragma("pack(1)");
    struct MyStuct
    {
    char a;
    int b;
    short c;
    long long d;
    char e;
    };
    _Pragma("Pack()");
    //还有以下用法
    alignas(int) char c;
  4. 利用alignof和std::alignment_of获取内存对齐大小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //alignof用来获取内存对齐大小
    MyStruct xx;
    std::cout << alignof(xx) << std::endl;
    std::cout << alignof(MyStruct) << std::endl;

    //std::alignment_of type_traits中的,用以编译期计算,继承自std::integral_constant
    struct MyStruct
    {
    char a;
    int b;
    double c;
    };
    int main()
    {
    int allignsize = std::alignment_of<MyStruct>::value; //8
    int sz = alignof(MyStuct); //8
    return 0;
    }
  5. 内存对齐类型std::aligned_storage

    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
    //前面type_traits 应用 构建一个optional类的时候遇到过,可以看成一个内存的缓冲区
    //原型如下
    template <std::size_t Len, std::size_t Align = /*default-alignment*/ >
    struct aligned_storage;
    //用法如下
    #include <type_traits>

    struct A
    {
    int avg;
    A(int a,int b):avg((a+b)/2){}
    };

    typedef std::aligned_storage<sizeof(A),alignof(A)>::type Aligned_A;

    int main()
    {
    Aligned_A a,b;
    new (&a) A (10,20);
    b = a;
    std::cout << reinterpret_cast<A&>(b).avg << std::endl;
    return 0;
    }

    //上述例子, placement new 和 aligned_storage 的使用, 比起直接开辟一块char[32]内存
    //aligned_storage 更能做到内存对齐
  6. std::max_align_t 和 std::align 操作符

    1
    2
    3
    4
    5
    6
    7
    //std::max_align_t用来返回当前平台的最大默认内存对齐类型
    std::cout << alignof(std::max_align_t) << std::endl;
    //std::align用来在一大块内存中获取一个符号指定内存要求的地址,也目前用不到,看看例子就行
    char buffer[] = "--------------------------";
    void * pt = buffer;
    std::size_t space = sizeof(buffer)-1;
    std::align(alignof(int), sizeof(char), pt, space);
三.C++11新增的便利算法

algorithm中新增的算法,用的时候在看看就行了

  1. all_of ,any_of 和none_of
  2. find_if_not
  3. copy_if
  4. iota
  5. is_sorted和is_sorted_until