4

在 C++ Primer 中有一个使用复制控制成员使类行为“类值”的示例;也就是说,在复制对象时,副本是独立的。它提出了以下代码:

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;
}
4

2 回答 2

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;
}
于 2015-02-24T20:11:41.680 回答
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编译器没有重新排序的分配。

于 2015-02-24T20:13:45.133 回答