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

问题描述 投票: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个回答
0
投票

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

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

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

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