C++ 新版本特性 - C++11

参考资料

特性概览

c++11_1

c++11_2

c++11_3

c++11_4

基本概念

POD(Plain Old Data) 指的是这样一些数据类型:基本数据类型、指针、 union 、数组、构造函数是 trivial 的 struct 或者 class。 POD 用来表明 C++ 中与 C 相兼容的数据类型,可以按照 C 的方式来处理(运算、拷贝等)。非 POD 数据类型与 C 不兼容,只能按照 C++特有的方式进行使用。

auto类型推导

C++11 赋予 auto 关键字新的含义,使用它来做自动类型推导。也就是说,使用了 auto 关键字以后,编译器会在编译期间自动推导出变量的类型,这样我们就不用手动指明变量的数据类型了。

auto 关键字基本的使用语法如下:

auto name = value;
1

name 是变量的名字,value 是变量的初始值。

注意:auto 仅仅是一个占位符,在编译器期间它会被真正的类型所替代。或者说,C++ 中的变量必须是有明确类型的,只是这个类型是由编译器自己推导出来的。

auto 的限制

  1. auto 不能在函数的参数中使用(auto 要求必须对变量进行初始化)。
  2. auto 不能作用于类的非静态成员变量(也就是没有 static 关键字修饰的成员变量)中。
  3. auto 关键字不能定义数组。
  4. auto 不能作用于模板参数。

auto 的应用

  • 使用 auto 定义迭代器
    vector< vector<int> >::iterator i = v.begin();  ——> **auto** i = v.begin();
    
    1
  • auto用于泛型编程

decltype类型推导

auto 和 decltype 关键字都可以自动推导出变量的类型,但它们的用法是有区别的:

auto varname = value;
decltype(exp) varname = value;
1
2

其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式。

auto 根据=右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。

另外,auto 要求变量必须初始化,而 decltype 不要求。这很容易理解,auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。decltype 可以写成下面的形式:

decltype(exp) varname;
1

decltype 的实际应用

#include <vector>
using namespace std;
template <typename T>
class Base {
public:
    void func(T& container) {
        m_it = container.begin();
    }
private:
    typename T::iterator m_it;  //注意这里
};
int main()
{
    const vector<int> v;
    Base<const vector<int>> obj;
    obj.func(v);
    return 0;
}

/*————————————————————————————————————————*/

template <typename T>
class Base {
public:
    void func(T& container) {
        m_it = container.begin();
    }
private:
    decltype(T().begin()) m_it;  //注意这里
};
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

使用using 定义别名

C++11 的 using 写法只是 typedef 的等价物。

template <typename Val>
using str_map_t = std::map<std::string, Val>;

//可以统一处理以下场景
//typedef std::map<std::string, int> map_int_t;
//typedef std::map<std::string, std::string> map_str_t;

void testUsing()
{
	str_map_t<int> map1;
	str_map_t<std::string> map2;
	
	map1["age"]=27;
	map2["address"]="NJ";
	
	std::cout<<"map1[\"age\"]:"<<map1["age"]<<std::endl;
	std::cout<<"map2[\"address\"]:"<<map2["address"]<<std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

支持函数模板的默认模板参数

C++11 支持为函数模板中的参数设置默认值,在实际使用过程中,我们可以选择使用默认值,也可以尝试由编译器自行推导得到,还可以亲自指定各个模板参数的类型。

template <typename R = int, typename T, typename U>
R func(T v1,U v2)
{
    return v1+v2;
}

void testFunTemplate()
{
    auto r1=func(5.5,10);                    // R=int, T=double, U=int
    auto r2=func<double>(5.5,10);            // R=double, T=double, U=int
    auto r3=func<double, int, int>(5.5,10);  // R=double, T=int, U=int
	
	cout<<"r1:"<<r1<<endl;
	cout<<"r2:"<<r2<<endl;
	cout<<"r3:"<<r3<<endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

tuple元组

tuple 最大的特点是:实例化的对象可以存储任意数量、任意类型的数据。

tuple 的应用场景很广泛,例如当需要存储多个不同类型的元素时,可以使用 tuple;当函数需要返回多个数据时,可以将这些数据存储在 tuple 中,函数只需返回一个 tuple 对象即可。

void testTuple()
{
    std::tuple<int, char> first;                             // 1)   first{}
    std::tuple<int, char> second(first);                     // 2)   second{}
    std::tuple<int, char> third(std::make_pair(20, 'b'));   // 3)   third{20,'b'}
    std::tuple<long, char> fourth(third);                    // 4)的左值方式, fourth{20,'b'}
    std::tuple<int, char> fifth(10, 'a');                    // 5)的右值方式, fifth{10.'a'}
    std::tuple<int, char,string> sixth(std::make_tuple(30, 'c',"address"));    // 6)的右值方式, sixth{30, 'c',"address"}
    
    std::cout << std::get<0>(fourth) << " " << std::get<1>(fourth) << std::endl;
    std::cout << std::get<0>(fifth) << " " << std::get<1>(fifth) << std::endl;
    std::cout << std::get<0>(sixth) << " " << std::get<1>(sixth) << " " << std::get<2>(sixth) << std::endl;
    
    int size = std::tuple_size<decltype(sixth)>::value;
    std::cout << size << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

列表初始化

在 C++11 中,初始化列表的适用性被大大增加了。它现在可以用于任何类型对象的初始化,请看下面的代码。

class Foo
{
public:
    Foo(int) {}
private:
    Foo(const Foo &);
};
int main(void)
{
    Foo a1(123);
    Foo a2 = 123;  //error: 'Foo::Foo(const Foo &)' is private
    Foo a3 = { 123 };
    Foo a4 { 123 };
    int a5 = { 3 };
    int a6 { 3 };
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在上例中:

  • a3、a4 使用了新的初始化方式来初始化对象,效果如同 a1 的直接初始化。
  • a5、a6 则是基本数据类型的列表初始化方式。可以看到,它们的形式都是统一的。
  • a3 虽然使用了等于号,但它仍然是列表初始化,因此,私有的拷贝构造并不会影响到它。
  • a4 和 a6 的写法,是 C++98/03 所不具备的。在 C++11 中,可以直接在变量名后面跟上初始化列表,来进行对象的初始化。

lambda匿名函数

C++11 标准终于引入了 lambda,本节将带领大家系统地学习 lambda 表达式的具体用法。

定义一个 lambda 匿名函数很简单,可以套用如下的语法格式:

[外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型
{
   函数体;
};
1
2
3
4
外部变量格式 功能
[] 空方括号表示当前 lambda 匿名函数中不导入任何外部变量。
[=] 只有一个 = 等号,表示以值传递的方式导入所有外部变量;
[&] 只有一个 & 符号,表示以引用传递的方式导入所有外部变量;
[val1,val2,...] 表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序;
[&val1,&val2,...] 表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序;
[val,&val2,...] 以上 2 种方式还可以混合使用,变量之间没有前后次序。
[=,&val1,...] 表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入。
[this] 表示以值传递的方式导入当前的 this 指针。

for循环

C++ 11 标准为 for 循环添加了一种全新的语法格式,如下所示:

for (declaration : expression){
    //循环体
}
1
2
3

示例:

void testForLoop()
{
    char arc[] = "abcdefg";
    // for循环遍历普通数组
    for (char ch : arc)
    {
        cout << ch;
    }
    cout << endl;
    vector<char> myvector(arc, arc + 23);
    // for循环遍历 vector 容器
    for (auto ch : myvector)
    {
        cout << ch;
    }
    cout << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

constexpr

constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。

C++11 标准中,定义变量时可以用 constexpr 修饰,从而使该变量获得在编译阶段即可计算出结果的能力。

constexpr int num = 1 + 2 + 3;
int url[num] = {1,2,3,4,5,6};
cout<< url[1] << endl;
1
2
3

右值引用

C++11 标准新引入了另一种引用方式,称为右值引用,用 "&&" 表示。右值引用引申出的 2 种 C++ 编程技巧,分别为移动语义和完美转发。

lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 "read value",指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。

和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化。

int num = 10;
//int && a = num;  //右值引用不能初始化为左值
int && a = 10;
a = 100;
cout << a << endl;
1
2
3
4
5

std::move

C++11最重要的一个改进之一就是引入了move语义,这样在一些对象的构造时可以获取到已有的资源(如内存)而不需要通过拷贝,申请新的内存,这样移动而非拷贝将会大幅度提升性能。

//-fno-elide-constructions 关闭了g++编译器会省略函数返回值时临时对象的拷贝的优化。
class demo{
public:
    demo():num(new int(0)){
        cout<<"construct!"<<endl;
    }
    //拷贝构造函数
    demo(const demo &d):num(new int(*d.num)){
        cout<<"copy construct!"<<endl;
    }
    //移动构造函数
    demo(demo &&d):num(d.num){
        d.num = NULL;
        cout<<"move construct!"<<endl;
    }
    ~demo(){
      delete num;
      cout<<"class demo destruct!"<<endl;
   }
   
public:     //这里应该是 private,使用 public 是为了更方便说明问题
    int *num;
};

demo get_demo(){
    return demo();
}

void testMove()
{
    demo demo1;
    cout << "demo2:"<< endl;//拷贝构造函数
    demo demo2 = demo1;
    cout << "demo3:"<< endl;
    demo demo3 = std::move(demo1);//移动构造函数
}
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

在移动构造函数中,只是获取了被移动对象的资源(内存)的所有权,同时把被移动对象的成员指针置为空(以避免移动过来的内存被析构),这个过程中没有新内存的申请和分配,在大量对象的系统中,移动构造相对与拷贝构造可以显著提高性能!

std::forward

完美转发:函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。

//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {
    cout << "lvalue\n";
}
void otherdef(const int & t) {
    cout << "rvalue\n";
}
//实现完美转发的函数模板
template <typename T>
void function(T&& t) {
    otherdef(std::forward<T>(t));
}

void testForward(){
    string A("abc");
    string&& Rval = std::move(A);
    string B(Rval);    // this is a copy , not move.
    cout << A << endl;   // output "abc"
    string C(std::forward<string>(Rval));  // move.
    cout << A << endl;       /* output "" */
    
    function(5);  //rvalue
    int  x = 1;
    function(x);  //lvalue
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

nullptr

C++11 标准下,相比 NULL 和 0,使用 nullptr 初始化空指针可以令我们编写的程序更加健壮。

void isnull(void *c){
    cout << "void*c" << endl;
}
void isnull(int n){
    cout << "int n" << endl;
}

void testNullptr()
{
    cout<<"testNullptr:"<<endl;

    isnull(0); 
    //isnull(NULL); // C++ 98/03 标准中 输出 int n
    isnull(nullptr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

std::bind 和 std::function

std::bind

std::bind 的函数原型:

simple(1): 	
template <class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);

with return type (2): 
template <class Ret, class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);
1
2
3
4
5
6
7

作用: 返回基于fn的函数对象,但其参数绑定到args。

每个参数都可以绑定到一个值或占位符:

  • 如果绑定到一个值,则调用返回的函数对象将始终使用该值作为参数。
  • 如果是一个占位符,则调用返回的函数对象会转发一个传递给调用的参数(该参数的顺序号由占位符指定)。

使用std::bind要注意的地方

  1. bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的。除非该参数被std::ref或者std::cref包装,才pass-by-reference。
  2. 对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的;
  3. bind的返回值是可调用实体,可以直接赋给std::function对象;
  4. 对于绑定的指针、引用类型的参数,使用者需要保证在可调用实体调用之前,这些参数是可用的;
  5. 类的this可以通过对象或者指针来绑定

测试程序:

#include <iostream>     // std::cout
#include <functional>   // std::bind

using namespace std;
// a function: (also works with function object: std::divides<double> my_divide;)
double my_divide (double x, double y) {return x/y;}

struct MyPair {
  double a,b;
  double multiply() {return a*b;}
};

struct MyAdd {
  double add(int a,int b) {return a+b;}
};

int main () {
  using namespace std::placeholders;    // adds visibility of _1, _2, _3,...

  // binding functions:
  auto fn_five = std::bind (my_divide,10,2);               // returns 10/2
  cout <<"fn_five(): "<<fn_five() << endl;                          	   // 5

  auto fn_half = std::bind (my_divide,_1,2);               // returns x/2
  cout <<"fn_half(10): "<< fn_half(10) << endl;                      		// 5

  auto fn_invert = std::bind (my_divide,_2,_1);            // returns y/x
  cout <<"fn_invert(10,2): "<< fn_invert(10,2) << endl;                      	// 0.2

  auto fn_rounding = std::bind<int> (my_divide,_1,_2);     // returns int(x/y)
  cout <<"fn_rounding(10,3): "<< fn_rounding(10,3) << endl;                   // 3

  MyPair ten_two {10,2};

  // binding members:
  auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
  cout <<"bound_member_fn():ten_two.multiply() "<< bound_member_fn(ten_two) << endl;           // 20

  auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
  cout <<"bound_member_data():ten_two.a "<< bound_member_data() << endl;                // 10
  
  MyAdd mAdd;
    
  auto bound_member_fn_add = std::bind (&MyAdd::add,&mAdd,_1,_2); 	// returns mAdd.add(_1,_2)
  cout <<"bound_member_fn_add(30,50): "<< bound_member_fn_add(30,50) << endl;           		// 80

  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

执行结果:

[root@192 function和bind函数学习]# ./bindTest
fn_five(): 5
fn_half(10): 5
fn_invert(10,2): 0.2
fn_rounding(10,3): 3
bound_member_fn():ten_two.multiply() 20
bound_member_data():ten_two.a 10
bound_member_fn_add(30,50): 80
1
2
3
4
5
6
7

std::function

类模版std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、bind表达式、函数指针以及其它函数对象。std::function对象是对C++中现有的可调用实体的一种类型安全的包装(我们知道像函数指针这类可调用实体,是类型不安全的)。

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

测试程序:

/**
 * 函数概述: std::function 是一种通用的多态函数包装器,可以存储、复制和调用任何可调用的目标——普通函数、
 * lambda表达式、bind表达式或其他函数对象,以及指向成员函数的指针和指向数据成员的指针(统称为“可调用对象”)。
 * 存在意义:多个不同类型的可调用对象却拥有着相同的调用形式。例如 std::function<int(int,int)>
 */

#include <functional>
#include <assert.h>
#include <iostream>

using namespace std;
 

/*加减乘除举例*/
typedef std::function<int(int,int)>  mFunctor;

int add(int a,int b)
{
    return a+b;
}

struct sub
{
    int operator()(int a,int b)
    {
        return a-b;
    }
};

auto Lam = [](int a,int b)->int{return a*b;};

class Math
{
public:
    static int div(const int a, const int b)
    {
        assert(b);
        return a/b;
    }
	int print_sum(int n1, int n2)
    {
        return n1+n2;
    }
};


int main()
{
    int a = 10, b = 5;
	Math math;
	
    std::function<int(int,int)> fun1 = add;     //普通函数
    mFunctor fun2 = sub();						//重载了函数调用运算符的函数对象
    mFunctor fun3 = Lam;      					//lambada表达式
    mFunctor fun4 = Math::div;  				//类静态成员函数
	std::function<int(int,int)> fun5 = std::bind(&Math::print_sum, &math, std::placeholders::_1, std::placeholders::_2);//类成员函数
	
	cout<<"fun1 add:"<<fun1(10,5)<<endl;
	cout<<"fun2 sub():"<<fun2(10,5)<<endl;
	cout<<"fun3 Lam:"<<fun3(10,5)<<endl;
	cout<<"fun4 Math::div:"<<fun4(10,5)<<endl;
	cout<<"fun5 Math::print_sum:"<<fun5(10,5)<<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
58
59
60
61
62
63
64

执行结果:

[root@192 function和bind函数学习]# ./functionTest
fun1 add:15
fun2 sub():5
fun3 Lam:50
fun4 Math::div:2
fun5 Math::print_sum:15
1
2
3
4
5