Реализация полноценного оператора копирования-назначения
В 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;
}