我正在读一本关于c ++的书,在“复制控制”部分,作者教我们在课堂上写operator=
告诉我们,当我们有一个使用动态的类时,我们必须确保该方法是安全的自我分配记忆。
所以,想象一下,我们有一个名为“Bank_Client”的类,其中使用std::string
创建了new
。这本书告诉我们这样做是为了避免自我分配的情况:
Bank_Client& Bank_Client::operator=(const Bank_Client &addres){
std::string *temp = new std::string(*addres.name);
delete name;
name = temp;
return *this;
}
所以,如果我这样做
Bank_Client bob("Bobinsky");
bob = bob;
该计划不会爆炸。但正当我认为temp
变量浪费时间时,本书的作者向我们展示了另一种方法:
Bank_Client& Bank_Client::operator=(const Bank_Client &addres){
if (this != &addres){
delete name;
name = new std::string(*addres.name);
}
return *this;
}
就像他读了我的想法一样。但是在那之后他告诉我们永远不要那样做,那就更好地通过另一种方式来做,但永远不解释原因。
为什么第一种方式更好?它比较慢,不是吗?这样做的最佳原因是什么?
怎么用assert
检查没有自我分配? (因为我们真的不需要它)。然后用相应的NDEBUG
去激活它,然后在检查中没有浪费时间。
你应该使用复制和交换。为此,您需要一个复制构造函数(如果您还想使用移动语义,也可能需要移动构造函数)。
class Bank_Client {
// Note that swap is a free function:
// This is important to allow it to be used along with std::swp
friend void swap(Bank_Client& c1, Bank_Client& c2) noexcept {
using std::swap;
swap(c1.name, c2.name);
// ...
}
// ...
};
// Note that we took the argument by value, not const reference
Bank_Client& Bank_Client::operator=(Bank_Client address) {
// Will call the swap function we defined above
swap(*this, adress);
return *this;
}
现在,我们来看看客户端代码:
Bank_Client bob("Bobinsky");
// 1. Copy constructor is called to construct the `address` parameter of `operator=()`
// 2. We swap that newly created copy with the current content of bob
// 3. operator=() returns, the temporary copy is destroyed, everything is cleaned up!
bob = bob;
显然,你在进行自我赋值时会创建一个无用的副本,但它的优点是允许你在复制构造函数中重用逻辑(或移动构造函数)并且它是异常安全的:如果初始副本抛出异常,则不会执行任何操作。
另一个好处是你只需要一个operator=()
实现来处理复制和移动语义(复制和交换以及移动和交换)。如果性能是一个大问题,你仍然可以有一个rvalue重载operator=(Bank_Client&&)
,以避免额外的移动(虽然我不鼓励它)。
最后,我还建议你尝试尽可能地依赖rule of 0,如上例所示,如果类的内容发生变化,你还必须相应地更新交换函数。
如果对象是自我分配的,第一种方式会更慢。但是,自我分配很少见。在所有其他情况下,the additional if
check from the second approach is a waste。
也就是说,这两种方法都是实现拷贝赋值运算符的不良方法:两者都不是异常安全的。如果赋值在中途失败,则会在一些不一致的状态下留下半分配的对象。从复制构造函数中部分复制逻辑也很糟糕。相反,您应该使用the copy-and-swap idiom实现复制赋值运算符。