C++中的拷贝控制

拷贝控制

当我们定义一个类时,我们显式或者隐式的指定了此类型的对象的拷贝、移动、赋值和销毁时的操作。一个类通过五种特殊的成员函数来控制这些操作:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。 这些操作统称为 拷贝控制操作
拷贝构造函数、移动构造函数:定义了用同类型的另一个对象来初始化本对象时做什么。
拷贝赋值运算符、移动赋值运算符:定义将一个对象赋予同类型的另一个对象时做什么。
析构函数:定义当此类型对象销毁时做什么。

拷贝

1)如果一个构造函数的第一个参数是自身类类型的引用( 必须),且任何额外参数都是默认值,则此构造函数是拷贝构造函数。
为什么必须是引用?
如果一个拷贝构造函数发生调用时,使用的不是引用,那么就会发生形参传值传递的方式。而传值的方式其实就是拷贝初始化的方式,此时调用的该类的拷贝构造函数,而拷贝构造函数又是传值传递,如此循环,则造成传值拷贝无限递归,造成调用失败。

2)如果我们没有为一个类定义拷贝构造函数,那么编译器会自动为我们合成一个拷贝构造函数。合成的拷贝构造函数会将其参数成员逐个地拷贝到正在创建的对象中。

3)直接初始化时利用函数匹配来匹配最合适的构造函数进行对象的初始化,而拷贝初始化是将右侧运算对象拷贝到正在创建的对象中,必要时还会进行类型转换(应该是先进行类型转换,然后才进行拷贝)。
发生拷贝初始化的场合有:
a)使用=运算符进行变量定义时(注意此处非赋值);
b)从一个返回类型为非引用类型的函数返回一个对象时(函数的返回类型为非引用时,会初始化一个临时量);
c)将一个对象作为实参传递给一个非引用类型的形参时(也就是在函数调用时参数传递使用的是传值方式);
d)用花括号列表初始化一个数组中或者一个聚合类中的成员时。

4)如果类未定义自身的拷贝赋值运算符,那么会进行自动合成,合成拷贝运算符返回一个指向左侧运算对象的引用。

5)重载运算符本质上是函数,函数名由关键字operator后接要定义的运算符的符号组成,其参数表示运算符的运算对象,参数类型与所处类的类型相同。重载赋值运算符必须是类的成员,它将其右侧运算对象的非static成员依照对应关系赋予运算符左侧对象的成员。

6)构造函数有一个初始化部分和一个函数体,成员的初始化是在函数体执行之前完成的,并按照在类中出现的顺序进行初始化。(需要更多的研究!!!)

7)可以使用 = default 显式地要求编译器生成合成的拷贝控制成员。=default 只适用于合成版本的成员函数。

8)可以使用 = delete将 拷贝构造函数和拷贝赋值运算符定义为 删除函数。删除函数:虽然进行了声明但不可以使用。=delete必须出现在函数第一次声明的时候并可以对任何函数使用 = delete。( 在函数匹配或者继承中也许也可以发挥作用!

9)也可以将 拷贝构造函数和拷贝赋值运算符声明为private成员来阻止拷贝。但这种情况并不会阻止其友元和成员函数对对象进行拷贝。若想让友元和成员函数的拷贝,可以只声明但不定义构造函数。( 声明但不定义一个成员函数是合法的,但有一个例外。) 
此时如何来实例化一个该类类型的对象呢?


10)需要重新定义拷贝构造函数的雷也需压重新定义重载赋值运算符,反之依然。

销毁

1)析构函数执行与构造函数相反的操作:构造函数会初始化对象的非static数据成员和一些其他工作;析构函数释放对象使用的资源并销毁非static数据成员。

2)析构函数函数名由波浪线和类名组成,没有返回值和形参。( 不接受参数

3)析构函数函数由一个函数体和析构部分组成,其中析构部分是隐式的;首先执行函数体,然后按照成员初始化顺序进行销毁( 函数体中并不会销毁成员);内置类型没有析构函数。(具体的析构顺序还需探讨!!!)

4)析构函数调用的场合有:
a)变量在离开其作用域时被销毁;
b)当一个对象被销毁时,其成员被销毁(对象中可能还有其他带有析构函数的成员);
c) 容器被销毁时,其元素被销毁;
d)动态分配的对象,当对指向它的指针 应用delete运算符时被销毁;
e)对于临时对象,当创建它的完整表达式结束时被销毁。

5)当一个类未定义自身的析构函数时,编译器会自动合成一个析构函数。但 合成的析构函数不会销毁动态分配的内存

6)需要重新定义析构函数的类也需要重新定义拷贝构造函数和重载赋值运算符。

7)析构函数不能是删除成员,否则将无法销毁对象。

移动

1) 右值引用是必须绑定到右值的引用,通过&&(中间没有空格)来获得,且 只能绑定将要销毁的对象。常规的引用可以称之为 左值引用。左值引用不能绑定右值,但一个const的左值引用可以绑定右值。左值引用不能绑定到要求转换的表达式、字面常量、或者返回右值的表达式。

2)左值对象可以持久的保持状态,而右值对象要么是字面常量,要么就是表达式求值过程中创建的临时对象。变量表达式都是左值。

3)调用move函数可以获得绑定到左值上的右值引用。可以对移后的原对象进行销毁、或者赋新值,但是不能够使用(访问)它。 而且 使用move的时候需要用std::move

4)移动构造函数不分配任何新内存,新对象将接管源对象的内存。且移动构造函数的第一个参数必须为右值引用,任何其他的参是都必须是默认实参。

5)移后源对象必须可以析构。

6)编译器可以自动合成移动构造函数和移动赋值运算符,但是是有条件的。当一个类定义了自己的拷贝构造函数和拷贝赋值运算符,编译器是不会为其合成移动构造函数和移动赋值运算符。因此有些类是不存在移动操作的,而此时会通过函数匹配来使用拷贝操作替代移动操作。只有当一个类没有定义自己版本的拷贝操作且其成员均可移动时才会合成移动操作。如果一个类定义了移动操作,那么其合成的拷贝操作会被定义成删除的。

7)移动构造函数通常不会抛出异常,可以使用noexcept关键字来通知编译器。一般写在函数调用运算符( )之后和成员初始化器之前。

交换

1)如果一个类定义了自己的swap,那么通常会使用自己的版本而不是标准库定义的版本。

其他

1)右值和左值引用成员函数。类似于const函数。规定了函数的返回值是哪种引用类型,位置和const相当。但当同时有const和引用限定符时,const在前引用限定符在后。
2)关于删除函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值