销毁并重新生成赋值运算符:如果我小心怎么办?

Pot*_*ter 6 c++ destructor undefined-behavior

这是一个糟糕的模式.复制和交换更好.

foo & operator = ( foo const & other ) {
    static_assert ( noexcept( new (this) foo() ), "Exception safety violation" );

    this-> ~ foo();
    try {
        new (this) foo( other );
    } catch (...) {
        new (this) foo(); // does not throw
        throw;
    }
    return * this;
}
Run Code Online (Sandbox Code Playgroud)

只要foo不多态性,有什么可以去错了吗?(但是,假设它基类.)

背景:我正在处理本地存储类型擦除,另一种方法是swap通过本地存储空间实现两个硬编码分配.源和目标的内存blob中的对象具有不同的类型,并且根本无法相互交换.根据这种交换定义的复制/移动构造是看似没有收益的两倍.

Pot*_*ter 0

销毁并重新生成与复制并交换具有根本不同的行为。比较:

  1. 破坏旧的价值。现在它已经永远消失了。
  2. 尝试构建新的价值。
  3. 如有必要,放弃并构建默认值。

复制和交换:

  1. 尝试构建新的价值。
  2. 如有必要,放弃并保留旧值。
  3. 应用新值。

两者都有其优点,但复制和交换无处不在,因此它的缺点是按照最小意外原则提出的。那么让我们模拟一下它的行为:

foo & operator = ( foo const & other ) {
    static_assert ( std::is_nothrow_move_constructible< foo >::value
                 || std::is_nothrow_default_constructible< foo >::value
                 , "Exception safety violation" );

    foo next( other );
    try {
        this-> ~ foo();
        new (this) foo( std::move( next ) );
    } catch (...) {
        new (this) foo();
        throw;
    }
    return * this;
}
Run Code Online (Sandbox Code Playgroud)

虽然更复杂,但这比 throwing 表现得更好swap,因为 throwing 可能会在异常后留下旧值和新值的大杂烩。

在移动构造函数不抛出异常的常见情况下(您记得声明它noexcept,对吧?),该算法可以很好地简化:

foo & operator = ( foo const & other ) {
    foo next( other );
    // The dangerous part is over now.

    this-> ~ foo();
    new (this) foo( std::move( next ) );
    return * this;
}
Run Code Online (Sandbox Code Playgroud)