Bootstrap

C++11(上)

列表初始化 {}

一切皆可用 {} 初始化, 对于自定义类型来说,{} 的本质是调用构造函数

class A {
public:
    A(int x, int y):_a(x), _b(y) { cout << "构造" << endl; }
    A(const A& a) :_a(a._a), _b(a._b) { cout << "拷贝构造" << endl; }
   
private:
    int _a;
    int _b;
};

如下示例

{} 相当于多参数隐式类型转换, {} 调用 A 类的构造生成临时变量,再调用拷贝构造,但编译器会直接优化成构造

A a = { 1, 2 };

{} 隐式类型转换——>去调用构造函数,构造函数会产生一个临时对象,临时对象具有常性,所以要加 const

const A& tmp = { 2, 3 };

内置类型也支持列表初始化

int b = { 1 };

initializer_list

initializer_list 是一个容器,参考文档:https://zh.cppreference.com/w/cpp/utility/initializer_list

它的底层是由两个指针指向一个数组,它的作用是辅助 {} 进行初始化。

我们可以看下 STL容器 的构造函数(部分)

vector( std::initializer_list<T> init,const Allocator& alloc = Allocator() );
list( std::initializer_list<T> init, const Allocator& alloc = Allocator() );
map( std::initializer_list<value_type> init,const Compare& comp = Compare(),const Allocator& alloc = Allocator() );

举个例子

map<string, string> h = { {"sort", "排序"}, {"insert", "插入"} };

上述容器的初始化是:pair多参数隐式类型转换 结合 initializer_list 构造

auto

auto 可以自动推导类型,但也只可以推导类型,如下代码

int i = 1;
int& x = i;
auto y = x;
y++;

上述代码中,y 的类型是 int 而不是 int& ,所以 y++ 不会影响 i 和 x。

nullptr

C++11之前 NULL 就是 0,这在某些场景下会很难受,C++11之后就推荐是用 nullptr ,它被解释为 (void*)0

左值与右值

概念上区分:可以取地址的是左值,不可以取地址的是右值

常见的右值有:字面量,匿名对象,临时对象(比如有返回值的函数调用返回的对象)

所以自定类型的右值也形象的称为:将亡值

std::move 可以让左值属性变为右值属性

左右值引用

左值引用(&):给左值取别名

右值引用(&&):给右值取别名

左值引用不可以给右值取别名, const 左值引用可以给右值取别名

右值引用不可以给左值取别名, 可以给move以后的左值取别名

右值为什么可以提高效率

左值在发生拷贝时会调用拷贝构造,但右值是将亡值,调用的是移动构造。

我用如下代码讲一下为什么移动构造不会发生拷贝

std::string s1 = "111111111111111111"; 
std::string s2;
s2 = move(s1); 

STL 容器 string 类的底层是一个 char 类型的数组,开辟在堆空间中,string 类有字段指向这块空间。当我们发生深拷贝时,需要重新开辟空间,然后拷贝。但如果 string 对象被打上右值属性,我们只需更改类成员指向关系,就可以把这块空间的资源转移走。

所以我们也就理解,移动构造针对深拷贝的场景,可以做到效率提升。

那么思考下述代码在C++11前后效率上的差别

bit::string to_string(int value)
{
    bit::string str;
	return str;   //C++11之后可以写成 return move(str);   
}

string ret = to_string(2);

str 是栈上的变量,处理函数作用域就会销毁,在C++11之前,只能先构造临时对象,再拷贝给 ret ,如果编译器优化的化,会直接构造给 ret。在C++11之后, str 会被识别成右值,str 的资源会转移给 ret,不会因为拷贝而浪费性能。

模板中的万能引用

    template<typename T> 
    void PerfectForward(T&& t)  
    { 
    }

模板中的 T&& 称为万能引用,而不是右值引用,会匹配最接近的类型,可以是:左值引用,右值引用,const 左值引用,const 右值引用

属性退化

右值引用本身的属性是左值,如下述代码

string&& str = string("123"); 

str 属性就是左值。

那这要怎么理解呢? 因为右值具有 const 属性,如果 str 本身的属性也是右值,就不可转移str的资源,但是右值匹配的移动构造就是要转移资源的!所以为了语法上的逻辑自洽:规定右值引用本身的属性是左值。

在参数传递时,右值的属性就会退化为左值属性,如下代码

 void Fun(int& x) { cout << "左值引用" << endl; }
 void Fun(const int& x) { cout << "const 左值引用" << endl; }
 void Fun(int&& x) { cout << "右值引用" << endl; }
 void Fun(const int&& x) { cout << "const 右值引用" << endl; }

 template<typename T> 
 void PerfectForward(T&& t)  
 {
     Fun(t);  
 }
int main() { 

    int b = 10;
    PerfectForward(b);  
    PerfectForward(move(10));
    const int c = 10;
    PerfectForward(c); 
    PerfectForward(move(c));

	return 0}

执行结果如同所示

那不可以强转吗?答案时可以的,强转后属性就不会退化,如下代码

 template<typename T> 
 void PerfectForward(T&& t)  
 {
     Fun((T&&)t); 
 }

执行结果如同所示

而C++11提供了完美转发,效果和强转一样,不会使属性退化

完美转发 std::forward()

如下代码

    template<typename T> 
    void PerfectForward(T&& t)  
    { 
        Fun(std::forward<T>(t));   
    }

执行结果如同所示

;