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

Destroy-and-regenerate assignment operator: what if I'm careful?

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

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;
}

只要 foo,还能出什么问题? (但是,假设它 一个基数 class。)

背景:我正在处理本地存储类型擦除,另一种方法是通过本地存储 space 将 swap 实现为两个硬编码分配。源和目标的内存 blob 中的对象属于不同类型,根本无法相互交换。 Copy/move 根据这种交换定义的构造是两倍的复杂性,似乎没有收益。

该标准在 [basic.life] §3.8/7:

中保证了此类中断的生命周期

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

— the storage for the new object exactly overlays the storage location which the original object occupied, and

— the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and

— the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and

— the original object was a most derived object (§1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

最后一点使我的用例不合格。但是,由于这只是针对一个非多态class,所以将析构函数和构造函数分别变成destroyinit私有成员函数也无妨。

换句话说,当 destroy-and-regenerate 合法时,您不妨使用成员函数而不是 new/delete.

做同样的事情

这样做的一个“优点”是它不再看起来很聪明,所以没有无知的路人想要复制设计。

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

  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;
}

虽然更复杂,但这比抛出 swap 表现更好,抛出可能在异常后留下新旧值的大杂烩。

在移动构造函数不抛出的常见情况下(您记得声明它 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;
}