Contact
не перегружает конструктор копирования.Следовательно, генерируется значение по умолчанию.
Конструктор копирования по умолчанию копирует все (не static
) переменные-члены, используя их конструкторы по умолчанию.В частности, конструктор указателя по умолчанию просто копирует адрес, который хранится.
Итак, используя конструкцию копирования Contact
, создается новый экземпляр с Contact::address
, указывающим на точно такой же Address
как оригинальный экземпляр.
Следовательно, изменение адреса jane
также изменит адрес joe
.
Это может быть предназначено или нет:
- преднамеренно дляподелиться ресурсами
- непреднамеренно, если
Contact
будет иметь исключительное право собственности на address
.
Если jane
и joe
не состоят в браке, это может быть непреднамеренным.
В текущем состоянии у проекта есть еще один недостаток:
Какой экземпляр отвечает за удаление пуантина address
при уничтожении Contact
?
Если это будетдобавлено в деструктор ~Contact()
вещи начинают ухудшаться.(Удаление jane
приведет к удалению ее адреса и оставлению john
с висящим указателем.)
Как и сейчас, уничтожение Contact
может привести к утечке памяти.(Внешний код должен был отвечать за удаление левых экземпляров Address
. Это трудно поддерживать.)
Такие проблемы проектирования не редки и приводят к правилу из трех , который говорит:
Если один из
- деструктор
- конструктор копирования
- оператор копирования копирования
явно определено, тогда как, скорее всего, и другое.
С C ++ 11 (вводя семантику перемещения) это было расширено до Правило пяти с добавлением
- Конструктор перемещения
- Оператор назначения перемещения.
Таким образом, одно явное определение может быть просто удалить их:
struct Contact {
Address *address;
// default constructor with initialization to empty
Contact(): address(new Address()) { }
// constructor with values
Contact(const Address &address): address(new Address(address)) { }
// destructor.
~Contact()
{
delete address; // prevent memory leak
}
// move constructor.
Contact(Contact &&contact): address(contact.address)
{
contact.address = nullptr; // prevent two owners
}
// move assignment.
Contact& operator=(Contact &&contact)
{
address = contact.address;
contact.address = nullptr; // prevent two owners
return *this;
}
// prohibited:
Contact(const Contact&) = delete;
Contact& operator=(const Contact&) = delete;
};
Это улучшение, касающеесяуправление памятью, но неэффективно в отношении намерения clone()
экземпляров Contact
.
Другим возможным решением является сохранение address
как std::shared_ptr<Address>
вместо Address*
.std::shared_ptr
(один из интеллектуальных указателей ) был введен для решения таких проблем (в отношении совместного владения).
struct Contact {
std::shared_ptr<Address> address;
// default constructor with initialization to empty
Contact(): address(std::make_shared<Address>()) { }
// constructor with values
Contact(const Address &address):
address(std::make_shared<Address>(address))
{ }
// another constructor with values
Contact(const std::shared_ptr<Address> &address):
address(address)
{ }
// destructor.
~Contact() = default;
/* default destructor of shared_ptr is fully sufficient
* It deletes the pointee just if there are no other shared_ptr's to it.
*/
// copy constructor.
Contact(const Contact&) = default; // copies shared_ptr by default
// copy assignment.
Contact& operator=(const Contact&) = default; // copies shared_ptr by default
// move constructor.
Contact(Contact&&) = default;
// move assignment.
Contact& operator=(Contact&&) = default;
};
Установка «пятерки»по умолчанию в этом случае фактически то же самое, что и пропуск их.
Ссылки, которые я обнаружил при проверке, чтобы не писать ничего глупого: