复制/移动操作符是否可以安全地用于实现复制/移动分配操作符

Can copy/move ctors be safely used to implement copy/move assignment operators?

本文关键字:复制 移动 操作符 用于 实现 分配 是否 安全      更新时间:2023-10-16

我认为以下代码比复制和交换习惯用法更好。

通过这种方式,您可以使用两个宏来封装复制和移动赋值运算符的定义。换句话说,您可以避免在代码中显式定义它们。因此,您可以将注意力集中在actor和dtor上。

这种方法有缺点吗?

class A
{
public:
A() noexcept
: _buf(new char[128])
{}
~A() noexcept
{
if (_buf)
{
delete[] _buf;
_buf = nullptr;
}
}
A(const A& other) noexcept
: A()
{
for (int i = 0; i < 128; ++i)
{
_buf[i] = other._buf[i];
}
}
A(A&& other) noexcept
: _buf(other._buf)
{
_buf = nullptr;
}
A& operator =(const A& other) noexcept
{
if (this != &other)
{
this->~A();
new(this) A(other);
}
return *this;
}
A& operator =(A&& other) noexcept
{
if (this != &other)
{
this->~A();
new(this) A(static_cast<A&&>(other));
}
return *this;
}
private:
char* _buf;
};
class A
{
public:
A() noexcept
: _buf(new char[128])
{}

在上面的例子中,如果new char[128]抛出异常,A()将调用std::terminate()

~A() noexcept
{
if (_buf)
{
delete[] _buf;
_buf = nullptr;
}
}

在上面,看起来还可以。可以简化为:

~A() noexcept
{
delete[] _buf;
}

A(const A& other) noexcept
: A()
{
for (int i = 0; i < 128; ++i)
{
_buf[i] = other._buf[i];
}
}

在上面的例子中,如果new char[128]抛出异常,将调用std::terminate()。但其他方面还好。

A(A&& other) noexcept
: _buf(other._buf)
{
_buf = nullptr;
}

在上面,看起来不错。

A& operator =(const A& other) noexcept
{
if (this != &other)
{
this->~A();
new(this) A(other);
}
return *this;
}

在上文中,通常我会说这很危险。如果new(this) A(other);投掷呢?在这种情况下,它不会,因为如果它尝试,程序将终止。这是否是安全行为取决于应用程序(terminate在Ariane 5上运行不好,但在更普通的应用程序中运行良好)。

A& operator =(A&& other) noexcept
{
if (this != &other)
{
this->~A();
new(this) A(static_cast<A&&>(other));
}
return *this;
}

以上内容应该可以正常工作。尽管我不确定它是否优于下面这个具有同等性能的非分支版本。行为上的不同之处在于,下面的版本并不是一个不适合自移动任务的版本。然而,我认为自移动赋值不必是no-op,因为其中一个后置条件表示结果值未指定(另一个后置状态表示结果值已指定,导致不可依赖的矛盾)。

A& operator =(A&& other) noexcept
{
delete [] _buf;
_buf = nullptr;
_buf = other._buf;
other._buf = nullptr;
return *this;
}

它将在您提供的上下文中正确工作。

当A是多态类并且具有虚拟析构函数时,这种技术将是灾难性的。

您可以通过将unique_ptr<char[]>用于_buf:来大大简化此类

class A
{
public:
static const std::size_t bufsize = 128;

A() noexcept
: _buf(new char[bufsize])
{}
A(const A& other) noexcept
: A()
{
copy_from(other);
}
A(A&& other) noexcept = default;
A& operator =(const A& other) noexcept
{
copy_from(other);
return *this;
}
A& operator =(A&& other) noexcept = default;
private:
void copy_from(const A& other) noexcept {
std::copy_n(other._buf.get(), bufsize, _buf.get());
}
std::unique_ptr<char[]> _buf;
};

该类更短,更惯用,并且在未来的变化面前更安全,因为它避免了";聪明的";CCD_ 9+放置CCD_。我个人会从A()A(const A&)中删除noexcept,但如果您希望程序在分配失败时为terminate,那是您的选择;)

如果你的目标只是避免编写赋值运算符——我不怪你,它们太平庸了——你应该按照零规则设计:

class A
{
public:
static const std::size_t bufsize = 128;

A() : _buf(bufsize) {}
private:
std::vector<char> _buf;
};

那里——所有隐含的复制和移动。