在C ++ Primer中,有一个使用复制控制成员来使一个类行为的例子" valuelike&#34 ;;也就是说,复制对象时,副本是独立的。它提出以下代码:
class HasPtrValue
{
public:
HasPtrValue(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }
HasPtrValue(const HasPtrValue &orig) : ps(new std::string(*orig.ps)), i(orig.i) { }
HasPtrValue& operator=(const HasPtrValue&);
~HasPtrValue() { delete ps; };
std::string *ps;
int i;
};
HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs)
{
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
我的问题是关于复制赋值运算符。据我所知,它的作用是在堆上创建一个新字符串,删除旧字符串,并使lhs指向新字符串。这真的有必要吗?通过简单地分配堆上的现有字符串,下面的代码是不是完全相同的东西?
HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs)
{
*ps = *rhs.ps;
i = rhs.i;
return *this;
}
答案 0 :(得分:2)
你是对的。以下列方式定义复制赋值运算符就足够了
HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs)
{
*ps = *rhs.ps;
i = rhs.i;
return *this;
}
唯一的区别(除了分配新的内存)是在这种情况下,字符串可以包含很多保留的内存,尽管rhs对象的字符串可以足够小。
当使用复制赋值运算符时,C ++标准并未说明目标字符串将缩小为原始字符串的大小。它只是说
size() str.size()
capacity() a value at least as large as size()
对于原始版本,它应检查是否存在自我分配以避免冗余内存分配。那应该是
HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs)
{
if ( this != &rhs )
{
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
}
return *this;
}
答案 1 :(得分:2)
你是对的。
您的版本不仅有效,而且效率更高,因为ps->capacity() >= rhs.ps->capacity()
可以重用现有内存。
如果您想提供强大的异常保证,请使用copy-and-swap idiom:
HasPtrValue& HasPtrValue::operator=(HasPtrValue copy) // notice the by value
{
swap(*this, copy);
return *this;
}
// integrating tip from link posted by WhozCraig
friend void swap(HasPtrValue &lhs, HasPtrValue &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
}
虽然对代码的更改应该已经提供了强大的异常保证,只要编译器没有重新排序i
的赋值。