拷贝控制
以class Foo为例
1.默认构造函数
即不需要任何参数的构造函数,即Foo()
若我们没有自己定义任何构造函数,则编译器会定义一个合成的默认构造函数,合成的默认构造函数的行为:
若成员在类内有初始值,用该值来初始化函数,否则执行默认初始化。
2.拷贝构造函数
Foo(const Foo&)
若我们没有定义拷贝构造函数,编译器会定义一个合成的拷贝构造函数,将参数的成员依次拷贝至正在创建的对象中。
拷贝初始化通常发生在非引用的参数传递以及函数return结果时,编译器可以跳过拷贝/移动构造函数直接创造对象,但是必须保证该拷贝/移动构造函数是存在并且可访问的如,
string s = “hello” //拷贝初始化
可以由编译器改成
string s(“hello”)
3.拷贝赋值运算符
Foo &operator =(const Foo&)
赋值运算符的重载只能在类内部定义。
作为成员函数的重载运算符会将左侧运算对象绑定在this指针上。
赋值运算符的重载通常需要保证自赋值不会出错,并通常返回Foo &。
如果类未定义拷贝赋值运算符,编译器会定义一个合成的拷贝赋值运算符。
4.移动构造函数/移动赋值运算符
Foo(Foo&&)
接受一个右值引用,直接将参数的数据移动至正在创建的对象中,除非类中未定义任何拷贝控制函数,并且所有的成员都是可移动的,编译器才会自动定义一个合成的移动构造函数,否则接受右值的情况将按照拷贝构造函数来处理。
utility头文件中包含一个move函数,可以将一个右值引用绑定到左值上,如
int i = 0;
int &&rri = std::move(i);
这将通知编译器,我有一个左值,但是我们要将它当成右值使用。
移动操作主要用在哪些作用为资源管理的类中,这些类的拷贝操作往往需要重新开辟一块内存,然而对于那些用完就直接析构的临时量对象,会平白多出一次拷贝操作,如果能让编译器将这类临时量对象与那些真正需要拷贝操作的对象区分开来,对于那些临时量使用移动构造函数,仅仅改变对象内部指针的值,而并不重新开辟一块内存,对于 需要拷贝的非临时量则使用拷贝构造函数重新开辟一块内存是非常方便的。
移动构造函数与普通的拷贝构造函数在于,临时量是会很快被析构的,使用移动构造函数偷走临时量中管理的内存,并将该对象内部指针置为nulptr,这样即使临时量被析构也只需要开辟一次内存。
移动操作在范型编程的时候尤为方便。。。据说是尤为方便
5,析构函数
析构函数仅仅是将要被析构的对象在被析构之前会自动执行的一个函数,其本身并不会去释放对象所站的内存。通常用来释放对象中指针元素所指向的内存。
析构函数应该申明为virtual 的,这样保证当基类指针动态绑定到派生类对象上时,能够正确的释放内存。
6, swap()函数
很多时候类应该定义自己的swap函数,同时可以将拷贝赋值运算符使用自己的swap函数完成。
通常swap的正确使用方法是这样:
class Foo{
friend void swap(Foo&, Foo&);
};
swap(Foo& obj1, Foo &obj2){
using std::swap;
swap(obj1.men1, obj.men1);
swap(obj1.men2, obj.men2);
}
不直接使用std::swap可以在类成员有自定义的swap函数时进行正确的交换,同时,可以在定义swap函数时,只交换指针来避免使用标准库中的swap函数所带来的数据拷贝。
浙公网安备 33010602011771号