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

问题描述 投票:4回答:1

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

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

/** Canonical move-assignment operator. */
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(TYPE::~TYPE()), "dtor must be noexcept");
    TYPE::~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;
}

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

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

c++ move-semantics assignment-operator move-assignment-operator
1个回答
1
投票

关于您的“规范”实现,有很强的论据— 这是错误的。

您将终止原始对象的生存期,并在其位置创建一个new对象。但是,指向原始对象的指针,引用等不会自动更新为指向新对象-您必须使用std::launder。然后,在原始对象上自动调用析构函数,从而触发undefined behavior

您可能会认为这是另一种“任何明智的实现都会做正确的事”的未定义行为,但是实际上许多编译器优化都涉及缓存值,这利用了此规范。因此,当代码在不同的优化级别下,由不同的编译器,具有相同版本的不同版本进行编译时,或者在编译器经历了糟糕的一天并且心情不好时,您的代码可能随时会中断。


实际的“规范”方法是使用copy-and-swap idiom

// copy constructor is implemented normally
C::C(const C& other)
    : // ...
{
    // ...
}

// move constructor = default construct + swap
C::C(C&& other)
    : C{}
{
    swap(*this, other);
}

// assignment operator = (copy +) swap
C& C::operator=(C other) // C is taken by value to handle both copy and move
{
    swap(*this, other);
    return *this;
}

如果使用得当

,如果正确内联了相关功能(应该很简单),那么复制和交换不会产生任何开销。这个习惯用法非常普遍,普通C ++程序员应该很容易理解它。不用担心会引起混乱,只需花2分钟来学习它,然后使用它。

这次,我们交换对象的值,并且对象的生存期不受影响。该对象仍然是原始对象,只是具有不同的值,而不是全新的对象。这样想:您想阻止孩子欺负他人。交换价值就像在文明地教育他们,而“破坏+建构”就像杀死它们

使它们 暂时死亡并赋予它们崭新的大脑(可能在魔术的帮助下)。至少可以说,后一种方法可能会有一些不良的副作用。

[像其他习语一样,在适当的时候使用它-不要只是为了使用而使用它。

© www.soinside.com 2019 - 2024. All rights reserved.