编译器生成的赋值运算符是否不安全?

时间:2014-06-03 14:57:43

标签: c++

据我所知,C ++编译器生成如下的赋值运算符:

struct X {
    std::vector<int> member1;
    std::vector<int> member2;
    X& operator=(const X& other) {
        member1 = other.member1;
        member2 = other.member2;
    }
};

这个例外不安全吗?如果member2 = other.member2抛出,则原始作业的副作用不会被撤消。

2 个答案:

答案 0 :(得分:8)

使用4 level exception safety系统:

  • 不投掷
  • 强力保证 - 操作完成或完全回滚
  • 基本保证 - 保留不变量,不泄露任何资源
  • 无保障

如果对象的每个成员提供基本异常保证或“更好”,并且对象不变量没有成员内依赖性,则编译器生成的赋值运算符具有基本异常保证。

如果每个成员的任务也具有无投保证,则无投保。

很少(如果有的话)有强有力的保证,这可能就是你所说的“异常不安全”。

复制交换习惯用法很受欢迎,因为编写无掷swap通常很容易,而构造函数应该已经提供强大的异常保证。结果是operator=具有强大的异常保证。 (在move的情况下,它是伪强的,因为输入通常不会回滚,但它是一个右值)

void swap( Foo& other ) noexcept; // implement this
Foo& operator=( Foo const& f ) {
  Foo tmp(f);
  swap( tmp );
  return *this;
}
Foo& operator=( Foo && f ) {
  Foo tmp(std::move(f));
  swap( tmp );
  return *this;
}

如果您也采用副值按值,有时可以将operator=升级为无抛出(唯一的例外是可能在构造参数时)。

Foo& operator=( Foo f ) noexcept {
  swap( f );
  return *this;
}

在某些情况下,Foo的某些构造函数是noexcept而其他构造函数不是:通过取另一个by-value,我们提供了总体上最好的异常保证(作为{=的参数{1}}有时可以通过省略或通过{}直接构建来直接构建。

由于某些原因,该语言(至少在此时)实施具有强保证的复制交换operator=是不切实际的。首先,C ++运行“你只需支付你使用的费用”,而copy-swap可能比成员副本更昂贵。其次,swap目前不是核心语言的一部分(有一些建议要添加operator :=:并将其折叠起来)。第三,与以前版本的C ++和C。

的反向兼容性

答案 1 :(得分:1)

2014年6月4日编辑

我最初的答案是基于我的理解,即原始海报寻求一个任务操作员,保证不会抛出异常。根据各种评论者的说法,只要对象在异常时保持不变,很明显异常就可以了。

建议的方法是通过临时变量和std :: swap()。

X& X::operator=(const X& other)
{
    // assign to temps.  If this throws, the object
    // has not changed.
    auto m1 = other.member1;
    auto m2 = other.member2;

    // the theory is, that swap won't throw
    // can we rely on that?
    std::swap(m1, member1);
    std::swap(m2, member2);

    return *this;
}

实际上我们不能依赖swap()不会抛出异常,除非我们对我们对象的组件有所了解。

为了确保swap()永远不会抛出,我们需要成员对象为Move AssignableMove Constructible

在给出的示例中,使用C ++ 11或更高版本的编译器,std :: vector&lt; int&gt; 移动可分配和移动可构造,所以我们是安全的。然而,作为一般解决方案,我们始终需要了解我们正在做出的任何假设,并检查它们是否正在持有。