前几天我和我的一个朋友就对象分配和构建进行了对话,他指出对象的赋值a = b
(语义上)等同于销毁a
然后重新进行 - 从b
(在同一个地方)构建它。
但是,当然,没有人(我认为)会像这样编写赋值运算符:
class A {
A& operator=(const A& rhs) {
this->~A();
this->A(rhs);
return *this;
}
A& operator=(A&& rhs) {
this->~A();
this->A(std::move(rhs));
return *this;
}
// etc.
};
[注意:我不知道如何在现有对象上手动调用构造函数/析构函数(我从来没有这样做过!),因此他们的调用可能没有正式意义,但我想你可以看到这个想法。] < / p>
这种方法有什么问题?我想必须有一个主要的表演限制,但是名单越大越好。
答案 0 :(得分:2)
这里有一个误用的建筑:
class A {
A& operator=(const A& rhs) {
if(&a==this) return *this;
this->~A();
new(this) A(rhs);
return *this;
}
A& operator=(A&& rhs) {
if(&a==this) return *this;
this->~A();
new(this) A(std::move(rhs));
return *this;
}
// etc.
};
这对于inplace ctor / dtor语义是正确的,而且{是std::allocator
在缓冲区中销毁和构造元素的作用,以便必须正确,对吗? / p>
嗯......不正确:这一切都是关于A实际上包含的内容以及A ctor实际上做了什么。
如果A只包含基本类型并且没有自己的资源,那么它就可以了。这不是惯用的,而是正确的。
如果A包含其他一些资源,则需要获取,管理和发布......您可能遇到麻烦。你也是如果A是多态的(如果~A是虚拟的,你会破坏整个对象,但是你只重构A子对象)。
问题是获取资源的构造函数可能会失败,并且不能销毁构造和抛出失败的对象,因为它从未“构造”过。
但是如果你是“分配”,你就不是“创造”,如果就地ctor失败,你的对象将存在(因为它预先存在于自己的范围内),但是处于不能的状态通过进一步的破坏来管理:想想
{
A a,b;
a = b;
}
在}
b,a将被销毁,但如果A(const A&amp;)在a = b中失败,而throw
在A::A
中生成,{{1} }不存在,但会a
}
将立即跳转到不正确的地方。
更惯用的方法是
throw
现在,
class A
{
void swap(A& s) noexcept
{ /* exchanging resources between existing objects should never fail: you just swap pointers */ }
public:
A() noexcept { /* creates an object in a "null" recognizable state */ }
A(const A& s) { /* creates a copy: may fail! */ }
A(A&& s) noexcept { /*make it as null and... */ swap(s); } // if `s` is temporary will caryy old resource deletionon, and we keep it's own resource going
A& operator=(A s) noexcept { swap(s); return *this; }
~A() { /* handle resource deletion, if any */ }
};
将在 a=b
中创建b
副本作为s
参数(通过operator=
)。
如果失败,A::A(const A&)
将不存在,s
和a
仍然有效(具有自己的旧值),因此退出的范围将正常销毁。
如果副本成功,则将交换已复制的资源和实际的b
,并且当a
在s
处死时,将释放旧资源。
通过converse
}
将a = std::move(b)
作为临时,b
参数通过A(A&amp;&amp;)构造,因此b将与s交换(并变为空),而s将与a交换。最后,s
会破坏旧的s
资源,a
会收到旧的a
,而b将处于空状态(因此当它的范围可能会平静下来结束)
必须在b
和A()
中实施“将A设为空”的问题。
这可以通过帮助程序成员(A(A&&)
,就像init
)或指定成员初始值设定项,或通过定义成员的默认初始化值(一次性)
答案 1 :(得分:1)
首先,只有在使用重载operator new()
构造对象时才需要手动调用析构函数,并使用std::nothrow
重载等一些注意事项。
您需要了解的是复制构造和赋值运算符之间的区别:当从现有对象创建新对象时,将调用复制构造函数,作为现有对象的副本。当已经初始化的对象从另一个现有对象分配新值时,将调用赋值运算符。
总而言之,您提供的赋值运算符的示例没有意义 - 它必须具有不同的语义。
如果您还有其他问题,请发表评论。
答案 2 :(得分:0)
首先直接调用复制构造函数是不合法的(至少在符合C ++的编译器中...... VS2012允许),因此不允许以下内容:
// assignment operator
A& operator=(const A& rhs) {
this->~A();
this->A::A(rhs); <--- Invalid use
此时您可以依赖编译器优化(请参阅copy elision和RVO)或在堆上分配它。
如果您尝试执行上述操作,可能会出现许多问题:
1)您可能在复制构造函数的表达式中抛出异常
在这种情况下,您将拥有
// assignment operator
A& operator=(const A& rhs) {
cout << "copy assignment called" << endl;
this->~A();
A newObj(rhs); // Can throw and A is in invalid state!
return newObj;
}
为了确保安全,您应该使用复制和交换习语:
set& set::operator=(set const& source)
{
/* You actually don't need this. But if creating a copy is expensive then feel free */
if (&source == this)
return;
/*
* This line is invoking the copy constructor.
* You are copying 'source' into a temporary object not the current one.
* But the use of the swap() immediately after the copy makes it logically
* equivalent.
*/
set tmp(source);
this->swap(tmp);
return *this;
}
void swap(set& dst) throw ()
{
// swap member of this with members of dst
}
2)您可能遇到动态分配内存的问题
如果A的两个实例共享一个指针,你可能有一个悬空指针才能释放它
a = a; // easiest case
...
// assignment operator
A& operator=(const A& rhs) {
this->~A(); <-- Freeing dynamically allocated memory
this->A::A(rhs); <--- Getting a pointer to nowhere
3)正如Emilio所说,如果该类是多态的,那么你将无法重新实例化该子类(除非你以某种方式使用类似CRTP的技术来欺骗它)
4)最后,分配和复制构造是两种不同的操作。如果A包含重新获取昂贵的资源,您可能会遇到很多麻烦。