将分配运算符实现为“销毁+构造”是否合法?

Fre*_*rud 6 c++ assignment-operator move-semantics move-assignment-operator

我经常需要为“原始”资源句柄(例如文件句柄,Win32 OS句柄等)实现C ++包装。这样做时,我还需要实现move运算符,因为默认的编译器生成的操作符不会清除move-from对象,从而产生双删除问题。

在实现移动分配运算符时,我更喜欢显式调用析构函数,并使用new放置就地重新创建对象。这样,我避免了析构函数逻辑的重复。另外,我经常根据copy + move(在相关时)实现副本分配。这将导致以下代码:

/** Canonical move-assignment operator. 
    Assumes no const or reference members. */
TYPE& operator = (TYPE && other) noexcept {
    if (&other == this)
        return *this; // self-assign

    static_assert(std::is_final<TYPE>::value, "class must be final");
    static_assert(noexcept(this->~TYPE()), "dtor must be noexcept");
    this->~TYPE();

    static_assert(noexcept(TYPE(std::move(other))), "move-ctor must be noexcept");
    new(this) TYPE(std::move(other));
    return *this;
}

/** Canonical copy-assignment operator. */
TYPE& operator = (const TYPE& other) {
    if (&other == this)
        return *this; // self-assign

    TYPE copy(other); // may throw

    static_assert(noexcept(operator = (std::move(copy))), "move-assignment must be noexcept");
    operator = (std::move(copy));
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

这让我感到很奇怪,但是我没有在网上看到任何有关以这种“规范”方式实现move + copy赋值运算符的建议。相反,大多数网站倾向于以一种特定于类型的方式来实现赋值运算符,在维护类时必须手动使其与构造函数和析构函数保持同步。

是否有任何反对(除了性能之外)以这种与类型无关的“规范”方式实现移动和复制分配运算符的论点?

基于UB评论的更新2019-10-08:

我已经阅读了http://eel.is/c++draft/basic.life#8,该书似乎涵盖了所讨论的案例。提取:

如果在对象的生命周期结束后...,在原始对象占用的存储位置创建了一个新对象,则指向原始对象的指针,指向原始对象的引用...将自动引用新对象,并且,...可以用于操纵新对象,如果...

此后有一些明显的条件与相同的类型和const /引用成员有关,但似乎是任何赋值运算符实现所必需的。如果我错了,请纠正我,但是在我看来,我的“规范”样本行为良好,而不是 UB(?)

基于复制和交换注释的更新2019-10-10:

赋值实现可以合并为一个采用值参数而不是引用的方法。这似乎也消除了对static_assert和自赋值检查的需要。我的新提议实施将变为:

/** Canonical copy/move-assignment operator.
    Assumes no const or reference members. */
TYPE& operator = (TYPE other) noexcept {
    static_assert(!std::has_virtual_destructor<TYPE>::value, "dtor cannot be virtual");
    this->~TYPE();
    new(this) TYPE(std::move(other));
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

Fre*_*rud 1

我相信http://eel.is/c++draft/basic.life#8中的例子清楚地证明了赋值运算符可以通过就地“销毁+构造”来实现,假设与非const、非重叠相关的某些限制对象等等。