复制/移动操作符是否可以安全地用于实现复制/移动分配操作符
Can copy/move ctors be safely used to implement copy/move assignment operators?
我认为以下代码比复制和交换习惯用法更好。
通过这种方式,您可以使用两个宏来封装复制和移动赋值运算符的定义。换句话说,您可以避免在代码中显式定义它们。因此,您可以将注意力集中在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;
};
那里——所有隐含的复制和移动。
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- std::元组分配和复制/移动异常保证
- std::async 如何工作:为什么它会调用这么多次复制/移动?
- 在临时将成员带出时省略复制/移动
- 是否可以避免在以下代码中复制/移动构造函数的需要?
- 默认复制/移动构造函数时 GDB 中的奇怪行为
- 返回 *&object 时是否允许复制/移动省略?
- 删除复制构造函数是否也会删除默认的复制/移动运算符?
- 用于删除复制/移动分配运算符的有效签名
- 复制 elision/RVO 会导致从同一对象复制/移动吗?
- 意外缺少隐式声明的复制/移动构造函数
- 是否可以传递具有捕获的不可复制(移动)值的 lambda
- CRTP 和复制/移动赋值/构造函数继承
- C++复制/移动构造函数和赋值运算符
- 覆盖复制/移动分配超载时,我是否需要删除当前的成员数据
- 为什么编译器在有模板构造函数时生成复制/移动构造函数
- C++:使用引用和值解压缩元组,而无需复制/移动太多
- 从C++中的基类继承复制/移动构造函数作为构造函数
- 初始化 std::数组而不复制/移动元素
- 是否允许复制/移动省略使使用已删除函数的程序格式正确?