Реализация полноценного оператора копирования-назначения

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

2 ответа

Решение

Ты прав. Ваша версия не только работает, но и более эффективна, так как существующая память может быть повторно использована, когда ps->capacity() >= rhs.ps->capacity(),

Если вы хотите предоставить строгие исключения, вы должны использовать идиому копирования и замены:

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 не переупорядочивается компилятором.

Ты прав. Достаточно будет определить оператор назначения копирования следующим образом

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;
}
Другие вопросы по тегам