文章目录
以C++中的类类型来说明
源码
cpp
class Moveable
{
public:
Moveable() : i(new int(3)){ //构造函数
cout << "构造函数" << endl;
};
~Moveable(){//析构函数
delete i;//这里也没有必要判断i是否为空指针,然后再执行delete i,因为delete 一个指向空地址的指针变量 这里也不会报错,delete命令底层有优化。
i = nullptr;//这里没必要把i置为空指针,因为i是对象中的非静态成员变量,对象生命周期结束后指针变量i生命周期也随着结束了。
};
Moveable(const Moveable & m) : i(new int(*m.i)){//拷贝构造函数
cout << "调用拷贝构造函数" << endl;
};
Moveable(Moveable && m) : i(m.i){//移动构造函数
cout << "调用移动构造函数" << endl;
m.i = nullptr;
};
int* i;
};
构造函数
构造函数,不显示的在类中写出来,会有隐式的构造函数。如:Moveable(){};什么参数都不需要传递。对象中的属性都是按照随机给值的。这样非常危险。所以一般都
会显示的重写构造函数,并在构造函数中初始化成员变量。构造函数支持重载。
什么时候会触发构造函数呢?
当创建对象时会自动触发调用构造函数。例如:Moveable m;//会触发调用构造函数
拷贝构造函数
拷贝构造函数,不显示的在类中写出来,会有隐式的拷贝构造函数。如:
Moveable(const Moveable & m) : i(m.i)){ };
拷贝何时会被触发调用呢。
Moveable m;//会触发调用构造函数
Moveable m1(m);//m是一个左值,不管编译器有没有做优化,都会调用拷贝构造函数
也就是根据一个已有的对象来生成另一个对象时,会触发拷贝构造函数。
浅拷贝
使用隐式拷贝构造函数,会产生浅拷贝问题,即新对象和旧对象中的指针成员 i都指向同一块堆内存,当这两个对象,其中一个释放内存时,会调一次delete i。
而另一个对象在释放内存时,也会调一次delete i。会产生同一个堆内存,被释放两次的情况,这种情况会使程序发生错误。
当第一次执行delete i 后,另一个对象中的指针变量i,就变成的悬空指针。也就是指针指向的内存已被释放了(不能再访问了)。
隐式拷贝构造函数就是 浅拷贝构造函数。为避免这种情况一般就需要重写拷贝构造函数。
深拷贝
针对以上隐式拷贝构造函数,带来的浅拷贝的问题。需要深拷贝来解决。
如:
Moveable(const Moveable & m) : i(new int(*m.i)){...拷贝操作...};
就是在拷贝的过程中,给新对象的指针成员属性重新分配一块堆内存,然后在 函数体内,把旧对象指针成员i指向的堆内存中的内容,拷贝一份到新对象指针成
员所指向的堆内存中。这样就解决了浅拷贝带来的悬空指针的问题。
移动构造函数
移动构造函数 是C++11中引入的一种新式构造函数,移动构造函数没有隐式的。想使用时,必须显示的在类中定义移动构造函数。
移动构造函数的形参类型是右值引用类型(也是C++11新出现的一个引用类型 例如: T &&)
例如:
Moveable(Moveable && m) : i(m.i){
m.i = nullptr;//移动语义
};
Moveable &&:就是一个右值引用类型,其实它在操作数据时,和 左值引用(Moveable &) 没啥区别,都表示要引用的对象的别名。
左值引用类型:用来引用左值的。
右值引用类型:用来引用右值的。
移动构造函数出现的意义是什么呢?
移动构造函数出现的意义就是在进行数据拷贝是,不想发生深拷贝那样的操作,如果指针成员i指向的那块堆内存内容特别大,那么进行深拷贝时,效率很低。而选择使用隐式拷贝构造函数 又会造成 上面所说的悬空指针的问题。
其实这里的移动构造函数可以看成是一个浅拷贝构造函数。因为重写了隐式的拷贝构造函数,那么类中就不在提供隐式的浅拷贝构造函数了。
所以在存在深拷贝构造函数的情况下,还想使用浅拷贝构造函数,又想避免浅拷贝带的悬空指针问题。那么移动构造函数就横空出世了。
移动构造函数和显示的深拷贝构造函数是可以共存的。
移动构造函数与默认的浅拷贝构造函数不同的地方,除了参数类型不一样外(一个是左值引用类型,一个是右值引用类型)。关键的区别在函数体内的移动语义。
移动构造函数和浅拷贝构造函数 都是在窃取别人的内容。
分析一下:移动构造函数的执行。首先接收一个右值,然后赋值给右值引用类型变量 m, 把旧对象指针i赋值给新对象的指针i。此时新对象的成员属性指针i也指向了旧对象的成员属性i所指向的那块堆内存。然后在函数体,把旧对象的属性指针i置为空指针。这样在旧对象生命周期结束时,触发析构函数时,就不会再释放堆内存了。所以也就避免了新对象属性指针i会变成悬空指针的问题。
一般情况传入移动构造函数中的那个右值,是将要生命周期结束的,或者说我们以后不会再使用到它了。但是还想要其对象里的内容(尤其是占大内存的对象)时。我们选择使用移动拷贝构造函数,效率高一些。
什么时候会触发移动构造函数呢?
Moveable m;
Moveable m2(std::move(m));//std::move(m)的唯一作用就是 将m强制转化为一个右值 。然后创建m2时调用的是移动构造函数。
注意:
当类中没有定义移动构造函数时,这条语句Moveable m2(std::move(m));就会调用拷贝构造函数。
所以说是优先调用移动构造函数,没有的话,再调用拷贝构造函数。
std::move();//函数也是C++11中的新特性,作用就是把一个左值,转换成右值。