C++ 踩坑:特殊成员函数

C++ 踩坑:特殊成员函数

特殊成员函数指的是在某些场景下被隐式定义的一系列成员函数,包括以下六个(详见 Special members):

  • 默认构造函数 (C++98)
  • 析构函数函数 (C++98)
  • 拷贝构造函数 (C++98)
  • 拷贝赋值函数 (C++98)
  • 移动构造函数 (C++11)
  • 移动赋值函数 (C++11)

在讨论这几个特殊函数之前, 有个众所周知的概念需要被提及, RAII或者是 RRID , 资源获得时是初始化过的(Resource Acquisition Is Initialisation)/资源释放时即销毁(Resource Release Is Destruction). 通常用于资源管理. 简单来说在构造时初始化资源, 比如内存, 网络, 文件或者互斥量, 在析构中释放资源. 析构函数的调用受作用域的影响. 当对象超出作用域时, 析构函数会被自动调用, 从而释放所管理的资源. 这避免了用户手动对动态分配的资源进行回收, 不需要担心对象的生命周期, 避免的内存泄露, 死锁等问题.

但是, 资源的管理者毕竟是一个新的对象, 并非资源本身. 因此, 在管理类对象的赋值和拷贝过程中, 不得不考虑资源成员对象是否可以赋值和拷贝, 以及资源所有权的共享和转移问题. 影响这些过程的就不得不提到类的几个特殊的成员函数及其相互联系, 还包括编译器自动生成的这些函数的前提和规则, 即 构造/析构函数, 拷贝/赋值拷贝构造函数, 移动/赋值移动构造函数.

这里有一张图表概括了几个特殊成员函数用户显式声明和编译器隐式声明之间的关系.
image

几点说明,

  • user declared: 用户显式定义的函数, 包括具有具体实现的, 声明为 default, 或者声明为 delete 的.
  • default: 编译器隐式生成的默认函数.
  • delete: 编译器隐式生成的 delete 函数.
  • 如果编译器没有声明特殊成员函数, 则该函数不会参与到重载决议中 (delete 的函数则会影响到重载行为). 比如, 如果我们写了一个拷贝构造函数, 那么编译器不会隐式声明移动构造函数. 因此, T obj(std::move(other)) 将会调用拷贝构造函数. 如果移动构造函数被 delete, 那么这块代码会调用到移动构造函数, 出现编译错误. (注: 换句话说, 编译器没有声明该函数, 那这个函数相当于不存在, 不会影响重载. 即函数调用将选择其他更加匹配的函数. 但是如果声明了, 不管是已经被定义了或者标记成 delete, default, 这个函数将参与到重载决议上, 如果该函数更加匹配, 则被会调用.)
  • 红色框框的是废弃的, 不建议使用. 该默认行为存在一定的危险.

自动生成规则

  • 默认构造函数: 当且仅当类不包含用户自定义的构造函数 (与 C++98 一样).
  • 析构函数: 和 C++98 类似. 唯一的不同是, 默认的析构函数是 noexcept 的.
  • 拷贝构造函数: 运行时行为和 C++98 一致: 对非静数据成员逐成员拷贝. 当且仅当类没有用户自定义的拷贝构造函数. 如果类声明了移动操作, 则拷贝构造函数将被删除.
  • 拷贝赋值函数: 同上
  • 移动构造和移动赋值构造函数: 对非静态数据成员执行逐成员移动. 当且仅当 类没有用户自定义的拷贝操作, 移动操作和析构函数.

两个拷贝函数互相独立的. 如果只定义了其中一个, 编译器将自动生成另一个.

两个移动函数互相依赖的. 如果定义了其中一个, 编译器将不会为类生成另一个. 简单解释,

  • 如果定义了其中一个移动函数, 这意味着自定义的移动构造和默认的逐成员移动不一样. 假如默认的逐成员移动有问题, 那么逐成员移动赋值大概率也有问题. 所以定义其中一个移动函数, 将会阻止编译器生成另一个移动函数.
  • 进一步, 如果显式声明了拷贝操作(构造或者赋值), 那么也不会生成移动操作. 定义了拷贝操作(构造或者移动)意味着常规的拷贝方法(逐成员拷贝)不适用该类, 那么编译器也会认为逐成员移动也不适用移动操作.
  • 同理, 声明了移动操作(构造或者赋值)会导致编译器禁用拷贝操作(Item 11, Effective Modern C++). 即, 假如逐成员移动并不是移动对象正确的方法, 那没有理由认为通过逐成员拷贝拷贝一个对象是正确的.

参考资料

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值