实现类似值的复制赋值运算符

Implementing valuelike copy-assignment operator

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

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

你是对的。按照以下方式定义复制赋值运算符就足够了

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

你是对的。 您的版本不仅有效而且效率更高,因为在 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 的赋值没有被编译器重新排序。