检查运算符中的自赋值= 我正在看C++版本第五版本中的代码示例。在第512页,他们给出了运算符=的示例代码,如下所示: HasPtr& HasPtr::operator=(const HasPtr &rhs) { auto newp = new string(*rhs.ps); // copy the underlying string delete ps; // free the old memory ps = newp; // copy data from rhs into this object i = rhs.i; return *this; // return this object }
他们正确地认为,如果你按照这个顺序做事,即使是自我分配,事情也会很顺利。但是,我一直看到这样的建议,即明确进行检查:检查运算符中的自赋值= 我正在看C++版本第五版本中的代码示例。在第512页,他们给出了运算符=的示例代码,如下所示: HasPtr& HasPtr::operator=(const HasPtr &rhs) { auto newp = new string(*rhs.ps); // copy the underlying string delete ps; // free the old memory ps = newp; // copy data from rhs into this object i = rhs.i; return *this; // return this object },c++,operator-overloading,C++,Operator Overloading,他们正确地认为,如果你按照这个顺序做事,即使是自我分配,事情也会很顺利。但是,我一直看到这样的建议,即明确进行检查: HasPtr& HasPtr::operator=(const HasPtr &rhs) { if (&rhs == this) return *this; // early exit if self assignment auto newp = new string(*rhs.ps); // copy the underlying s
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
if (&rhs == this) return *this; // early exit if self assignment
auto newp = new string(*rhs.ps); // copy the underlying string
delete ps; // free the old memory
ps = newp; // copy data from rhs into this object
i = rhs.i;
return *this; // return this object
}
这避免了额外的内存分配/释放步骤
有人能解释为什么他们强调编写处理自我分配的代码,而不只是在自我分配的早期退出吗?如果我们放弃书的作者只是想让读者开动脑筋的假设,我们可能会认为,分配操作员按照作者建议的方式编写的代码可能会被用来降低成本内部动态缓冲区(或者做一些其他有用的事情)。例如,使用
std::vector
您需要执行vector(me.swap(me))
来修剪内存,而使用奇特的赋值运算符,您只需说me=me代码>重要的是,代码在自我分配(或自我复制)下正常工作,包括异常安全。有鉴于此,实现是否包括显式自检的问题主要是性能问题。但一种或另一种选择更好,取决于使用。例如,如果自操作很少发生或从未发生,测试本身会使代码变慢
因此,在没有实际性能测量的情况下使用自检可能是过早优化的一个例子。我看到了相反的建议,例如在斯特劳斯特鲁普的书中,一个人避免自我测试,除非测量表明它们有意义
我一直看到这样的建议,即检查是明确进行的
<>你一直在错误的地方查看,例如萨特和亚历山大RCux.C++的编码标准。p>
在大多数程序中,自分配非常罕见,因此显式检查会给每个自分配增加一点成本,即使检查几乎总是错误的。如果您编写的赋值运算符即使在自赋值的情况下也是正确的,那么在绝大多数情况下,您不会得到检查的成本,并且如果发生自赋值,它仍然有效
Sutter&Alexandrescu显示了根据交换
成员编写的赋值运算符:
Foo& operator=(const Foo& other)
{
Foo(other).swap(*this);
return *this;
}
这对自赋值、异常安全是安全的,并且重新使用复制构造函数,因此您不必显式地实现赋值。我认为他们只是忘记了如果您的代码执行了太多的自赋值,您可以通过添加检查来获益,那么您需要重新考虑您的设计。必读:在您提供的示例中,将发生自我分配,没有支票this@Praetorian您是否对空指针进行了如此多的解引用,这就是为什么您总是希望确保指针有效的原因?@Praetorian:这是否意味着我的代码永远不应该检查边缘情况,因为设计良好的程序根本不允许它们?仅仅是因为赋值运算符是安全的handle self assignment并不意味着您应该将实现细节的副作用作为一项功能来依赖,“使用std::vector
您需要执行vector(me).swap(me);
来修剪内存”-这是在C++11中随着该方法的引入而解决的。实际上,在第三版的C++程序设计语言中,他建议在正文中进行检查,然后在附录中建议如何优化它。但是,上次翻阅这本书时,我没有读附录。好吧,现在只检查了索引,它在第页。382和p。第四版第508页,分别讨论了向量
和矩阵
的设计。在这两种情况下,他认为自我分配非常罕见,以至于“从测试中获得的效率(在罕见的情况下)被其成本(在普通情况下)所抵消”。他经常重复说:“和以往一样,没有衡量标准,关于效率的讨论就没有意义了。”我想我需要得到最新版本很好的例子。我在20年前“学习”C++,但是我的原始教科书甚至没有提到在主文本中的<代码>操作符=< /> >的自我分配。随着C++11的广泛采用,我正在努力清理我知识的各个角落,我更喜欢这个例子,而不是我见过的一些没有通过引用传递Foo的例子。@NathanS请注意,这个定义,以及更简单的定义,可能会错过两个优化的机会,例如对于向量:(1)如果其容量大于新对象,则不需要重新分配;(2) 元素分配可能比销毁+构造更有效。与自我分配不同,这些情况并不罕见。