C++ 复制/移动构造函数能否安全地用于实现复制/移动赋值操作符?
我认为下面的代码比复制和交换习惯用法要好 通过这种方式,您可以使用两个宏来封装复制和移动分配运算符的定义。换句话说,您可以避免在代码中显式定义它们。因此,您可以只将注意力集中在CTOR和dtor上 这种方法有什么缺点吗C++ 复制/移动构造函数能否安全地用于实现复制/移动赋值操作符?,c++,design-patterns,c++11,constructor,idioms,C++,Design Patterns,C++11,Constructor,Idioms,我认为下面的代码比复制和交换习惯用法要好 通过这种方式,您可以使用两个宏来封装复制和移动分配运算符的定义。换句话说,您可以避免在代码中显式定义它们。因此,您可以只将注意力集中在CTOR和dtor上 这种方法有什么缺点吗 class A { public: A() noexcept : _buf(new char[128]) {} ~A() noexcept { if (_buf) { del
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;
};
A类
{
公众:
不例外
:_buf(新字符[128])
{}
~A()不例外
{
如果
{
删除[]_buf;
_buf=零PTR;
}
}
A(常数A和其他)无例外
:A()
{
对于(int i=0;i<128;++i)
{
_buf[i]=其他;
}
}
A(A&&其他)无例外
:_buf(其他._buf)
{
_buf=零PTR;
}
运算符=(常量A和其他)无异常(&O)
{
如果(此!=&其他)
{
这个->~A();
新(本)A(其他);
}
归还*这个;
}
A&运算符=(A&&其他)无例外
{
如果(此!=&其他)
{
这个->~A();
新(本)A(静态_铸造(其他));
}
归还*这个;
}
私人:
char*_buf;
};
它将在您提供的上下文中正常工作
当A是多态类且具有虚拟析构函数时,此技术将是灾难性的。它将在您提供的上下文中正常工作
class A
{
public:
A() noexcept
: _buf(new char[128])
{}
当A是多态类且具有虚拟析构函数时,这种技术将是灾难性的
class A
{
public:
A() noexcept
: _buf(new char[128])
{}
在上面的例子中,A()
将调用std::terminate()
如果newchar[128]
抛出异常
~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];
}
}
在上面,看起来不错
A& operator =(const A& other) noexcept
{
if (this != &other)
{
this->~A();
new(this) A(other);
}
return *this;
}
在上面,通常我会说这是危险的。如果new(这个)A(其他)怎么办代码>抛出?在这种情况下,它不会,因为如果它尝试,程序将终止。这是否安全取决于应用程序(terminate对Ariane 5不起作用,但在更普通的应用程序中效果很好)
在上面的例子中,A()
将调用std::terminate()
如果newchar[128]
抛出异常
~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];
}
}
在上面,看起来不错
A& operator =(const A& other) noexcept
{
if (this != &other)
{
this->~A();
new(this) A(other);
}
return *this;
}
在上面,通常我会说这是危险的。如果new(这个)A(其他)怎么办代码>抛出?在这种情况下,它不会,因为如果它尝试,程序将终止。这是否安全取决于应用程序(terminate对Ariane 5不起作用,但在更普通的应用程序中效果很好)
通过为\u buf
使用unique\u ptr
,可以大大简化此类:
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;
};
这里-所有隐式复制和移动。您可以通过为\u buf
使用unique\u ptr
大大简化此类:
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;
};
那里-所有隐式复制和移动。你能给我举个例子吗?想象一下,A是一个“非最终”数据类,比如说,一个保持形状的笔刷和颜色。从A派生出的B类是一种特殊形状(例如圆)。然后代码bb(…)
和后来的b=A(…)
本打算刷涂b
的颜色,但却调用了它的析构函数(因为析构函数是一个虚拟函数)。更糟糕的是,new(this)A
行将B的虚拟表指针替换为A的虚拟表指针,这将导致很难找到bug。你能给我一个例子吗?想象一下,A是一个“非最终”数据类,比如说,一个保持形状的笔刷和颜色。从A派生出的B类是一种特殊形状(例如圆)。然后代码bb(…)
和后来的b=A(…)
本打算刷涂b
的颜色,但却调用了它的析构函数(因为析构函数是一个虚拟函数)。更糟糕的是,行new(this)A
将把B的虚拟表指针替换为A的虚拟表指针,这将导致难以发现的错误。默认构造函数noexcept
的准确程度如何?你的移动构造器不会移动任何东西。在删除某些内容之前,无需检查null ptr
。为什么static\u cast
而不是std::move
;只是移动操作。如果复制或移动构造函数可以引发异常,则您会遇到麻烦。如果您实际编写了other,则可能是移动操作。\u buf=nullptr代码>。正如Kerrek在上面所评论的,您的两个赋值运算符都不提供强大的异常保证,而copy&swap则提供。@Praetoriannoexcept
并不意味着“此函数中的任何内容都不能引发异常”。它意味着“此函数永远不会发出异常。”noexcept
保证是函数接口的一个特征,与返回类型非常相似。这显然不是对如何实现该功能的约束。我在争论语义学,我知道你个人理解这一点。但是我觉得,对于那些认为noexcept
是编译器针对const
之类的实现进行验证的程序员来说,明确区分是很重要的。您的默认构造函数noexcept
的准确程度如何?你的移动构造器不会移动任何东西。在删除某些内容之前,无需检查null ptr
。为什么static\u cast
而不是std::move
;只是移动操作。如果复制或移动构造函数可以引发异常,则您会遇到麻烦。如果您实际编写了other,则可能是移动操作。\u buf=nullptr代码>。正如Kerrek在上面评论的那样,您的两个赋值运算符都不提供强有力的异常保证,而copy&s