构造函数
- 初始化类的成员
拷贝构造
- 当类对象被其他类对象,或可以隐式转换为类类型,初始化时
- 第一个形参为自身类型引用,其余形参均有默认值,的构造函数为拷贝构造函数
- 无论我们是否自定义拷贝构造,编译器都会为我们合成默认拷贝构造
- 会将每个非static成员,逐个拷贝到正在创建的对象中
- 拷贝方式:
- 内置类型直接拷贝
- 类类型成员根据自己的构造函数
- 数组不能拷贝,但可以逐一拷贝数组成员
拷贝初始化 VS 直接初始化
- =拷贝初始化,会调用拷贝构造函数
- () 直接初始化,会根据重载的函数匹配,来匹配优先级最高的构造函数
- 拷贝初始化不仅通过=号,还包括:非&类型形参,非&返回类型,列表初始化数组元素
- 因此,拷贝构造的形参需要为&类型,否则发生无休止的递归
- 拷贝初始化限制,当构造函数为explicit的类类型转换,我们不能使用=性质发生隐式类类型转换,应用()形式,发生类型转换
拷贝赋值
- 当类对象被其他类对象,或可以隐式转换为类类型,赋值时
- 本质是重载=号运算符 :class& operator = (const class&),括号内为赋值(=)的右侧运算对象,左侧运算对象&绑定到this,为类对象
折构函数
- 不接受参数,不能被重载,每个类只会有唯一的折构函数
- 当类对象离开作用域,类对象被销毁,其成员也都被销毁
三/五法则
- 需要自定义折构函数的类,也需要自定义拷贝构造和拷贝赋值
- 需要自定义拷贝构造的类,也需要自定义拷贝赋值
阻止拷贝
- =default ,显示生成默认版本,如果希望是内联,则在类内使用,反之,在类外定义使用
- //
- 有时我们希望不可以拷贝或赋值类对象,使用=delete表示虽然声明,但不能使用,定义为删除的。
- =delete不同于=default,必须出现在第一次声明时,因为编译器需要先禁止使用它的操作
- =delete另一个不同于=default,可以对任何函数指定=delete,不必非要时默认的函数
- 对于默认版本会受=delete的影响:
- 如果类成员(类对象或成员函数)折构是删除或private的,则类的默认折构,拷贝构造,被定义为删除,否则可能会创建无法销毁的对象
- 如果类成员拷贝构造函数删除,则默认拷贝构造,被定义为删除,
- 如果类成员拷贝赋值函数删除,或者有const或引用成员,则默认拷贝赋值,被定义为删除,
- 对于引用来说,因为在=赋值时,我们改变的时引用绑定的值,而非引用绑定其他对象,和我们想要拷贝类对象的每个成员想法违背,所以定义为删除的
- 对于const成员不允许 = 赋值操作
- 如果引用成员或const成员,没有类内初始值(成员列表初始化 > 类内初始化),那么默认初始化的值是未定义的,类的默认构造函数定义为删除
- 老版本通过将拷贝或赋值运算符声明为private来阻止拷贝
指针的拷贝管理
- 对于指针= 行为有两种:**p = &p2 (指向指针的指针) || p = p2(指向同一对象,p2指针作为右值取p2的值)
- 当默认拷贝构造或拷贝赋值函数的= 操作,为第二种形式,它们指向同一内存,因此会发生,多次delete指针 | 空悬指针的问题,所以我们需要自定义拷贝构造或拷贝赋值
- //
- 可以使用new动态分配新的堆内存:
- 要注意防范自赋值问题:当自赋值,需要先拷贝内存,再delete当前类指针,再为当前类指针指向新的地址,
- 而非直接delete当前类指针,不拷贝内存,当我们自赋值时,就已经释放了这两个类的内存,从而变为空悬指针
- //
- 还有一种方式实现指针的管理:
- 使用int计数器模拟shared_ptr的计数操作,当拷贝构造时递增本对象引用计数,当拷贝赋值时递减本对象引用计数,递增&右侧对象引用计数,对于折构,如果计数==0,delete指针
移动拷贝 & 移动赋值
- 移动:不发生拷贝,移动资源
- 移动拷贝的第一个参数是右值&&引用,它和&不同,将不会分配新的内存
- &左值持久,&&右值短暂:&&绑定的必须是临时对象,要被销毁的对象,此对象没有其他用户,可被销毁
- 在拷贝构造内,我们会将括号内对象的指针都置为nullptr,源对象会被销毁,本对象存在
- 要注意标记为noexcept