C++ 隐式生成的赋值运算符的异常安全性

Posted

技术标签:

【中文标题】C++ 隐式生成的赋值运算符的异常安全性【英文标题】:Exception-safety of C++ implicitly generated assignment operator 【发布时间】:2012-10-31 17:50:33 【问题描述】:

我的理解是 C++ 隐式生成赋值运算符会执行成员方式复制(这似乎也得到了this answer 的证实)。但是,如果在成员复制期间抛出异常(例如,因为无法分配该成员的资源),被复制的对象是否会卡在无效状态?

也就是说,隐式生成的赋值运算符是否只实现了基本保证,而没有实现强保证?

如果我们想为我们的类副本提供 strong 保证,我们是否必须使用 copy-and-swap 习惯用法手动 实现赋值运算符?

【问题讨论】:

“无法分配资源”,如果您的意思是为其分配内存的指针数据成员,那么赋值运算符已经无法满足它。默认的成员明智副本只会将相同的地址复制到新指针中。这就是我们编写复制构造函数的经典原因 @Mr.C64 根据 C++ 11 标准赋值运算符不是隐式生成的,但复制赋值运算符会。 @fayyazkl:不,我不是指原始指针数据成员。我正在考虑一个 C++ RAII 类,它的数据成员是 RAII 资源管理器(不是原始指针)。 @spin_eight:隐式生成的赋值运算符是一个复制赋值运算符。我不认为这里有任何歧义(除了 C++11 引入了移动分配,但这不是 Mr.C64 所要求的,因为它不执行成员明智的复制) . 【参考方案1】:

如果你想提供一个异常保证,并且默认的赋值运算符不是 nothrow,那么通常你需要写一个。

默认的复制分配甚至不一定能实现基本的保证,即不泄漏任何资源并且保留类不变量。分配一些数据成员但不是全部可能会使目标处于不满足类不变量的状态,具体取决于特定的类。

所以你必须评估你的类的默认运算符——如果它可以抛出,并且通过抛出使对象处于“无效”状态,那么你必须抑制它。或者削弱定义的类不变量,但这对用户帮助不大。

有(至少)一种特殊情况。如果除一个之外的所有数据成员都没有赋值,并且特殊的具有强异常安全赋值,并且是类中的 first 数据成员,那么默认赋值运算符也将是强安全的.如果您依赖它,您可能需要非常仔细地评论它,但是,它可能被证明非常脆弱!

【讨论】:

在一些非常简单的类上,基本保证可能是“偶然”实现的。但是,如果您的课程满足这一点,您需要记录为什么隐式生成的赋值运算符就足够了,这与编写自定义运算符的工作量大致相同。你至少需要考虑一下。 @TobiasLangner:同意。例如,如果类的数据成员是“正交的”,这意味着类不变量不暗示成员值之间的任何关系,那么您将获得基本保证。 感谢大家指出保留类不变量(我错过了这个要求)。 @SteveJessop:您可能希望将答案的关键部分用粗体格式化:如果您想提供异常保证,并且默认赋值运算符不是 nothrow,那么通常您需要写一个。。 BTW:copy-and-swap 成语可以提供有力的保证吗?还是有什么陷阱? @Mr.C64:复制和交换提供了强有力的保证,前提是交换不被抛出。证明很简单:在交换之前两个对象都没有被修改,异常只在交换之前被抛出,所以当抛出异常时什么都不会被修改。交换是强安全的就足够了,但通常你不希望交换因为其他原因而被抛出。主要的是,如果您需要进行 两次 交换并且每个交换都是强安全的,则并不意味着组合的操作是强安全的。但是两个 nothrow 操作加起来就是一个 nothrow 操作。

以上是关于C++ 隐式生成的赋值运算符的异常安全性的主要内容,如果未能解决你的问题,请参考以下文章

隐式生成的赋值运算符应该是 & ref 限定的吗?

在 C++ 中编写复制构造函数和赋值运算符的清单

c++ 拷贝构造函数与赋值运算符重载函数的区别是

C++ - 让派生类从基类“继承”重载赋值运算符的安全/标准方法

C++重载赋值运算符

C++重载赋值运算符