Я могу вспомнить из своей практики и подумать о следующих случаях, когда приходится иметь дело с явным объявлением / определением конструктора копирования. Я сгруппировал дела в две категории
- Корректность / семантика - если вы не предоставите пользовательский конструктор копирования, программы, использующие этот тип, могут не скомпилироваться или работать неправильно.
- Оптимизация - хорошая альтернатива сгенерированному компилятором конструктору копирования позволяет ускорить выполнение программы.
/ Семантика корректности
Я помещаю в этом разделе случаи, когда объявление / определение конструктора копирования необходимо для правильной работы программ, использующих этот тип.
После прочтения этого раздела вы узнаете о нескольких подводных камнях, позволяющих компилятору самостоятельно создавать конструктор копирования. Поэтому, как отмечал seand в своем ответе , всегда безопасно отключить копирование для нового класса, а намеренно включить его позже, когда это действительно необходимо.
Как сделать класс не подлежащим копированию в C ++ 03
Объявите личный конструктор копирования и не предоставьте ему реализацию (так что сборка завершится неудачно на этапе связывания, даже если объекты этого типа скопированы в собственной области действия класса или его друзьями).
Как сделать класс не подлежащим копированию в C ++ 11 или новее
Объявите конструктор копирования с =delete
в конце.
Мелкое и глубокое копирование
Это самый понятный случай и фактически единственный, упомянутый в других ответах. shaprtooth имеет покрытый это очень хорошо. Я только хочу добавить, что глубоко копируемые ресурсы, которые должны принадлежать исключительно объекту, могут применяться к любому типу ресурсов, из которых динамически выделяемая память является лишь одним из видов. При необходимости глубокое копирование объекта может также потребовать
- копирование временных файлов на диск
- открытие отдельного сетевого подключения
- создание отдельного рабочего потока
- выделение отдельного кадрового буфера OpenGL
- и т.д.
Саморегистрационные объекты
Рассмотрим класс, в котором все объекты - независимо от того, как они были построены - ДОЛЖНЫ быть как-то зарегистрированы. Некоторые примеры:
Простейший пример: ведение общего количества существующих в данный момент объектов. Регистрация объекта почти увеличивает статический счетчик.
Более сложный пример - использование одноэлементного реестра, в котором хранятся ссылки на все существующие объекты этого типа (чтобы уведомления могли доставляться всем им).
Умные указатели с подсчетом ссылок можно рассматривать как особый случай в этой категории: новый указатель «регистрируется» в общем ресурсе, а не в глобальном реестре.
Такая операция саморегистрации должна выполняться ЛЮБЫМ конструктором типа, и конструктор копирования не является исключением.
Объекты с внутренними перекрестными ссылками
Некоторые объекты могут иметь нетривиальную внутреннюю структуру с прямыми перекрестными ссылками между их различными подобъектами (на самом деле для запуска этого случая достаточно одной такой внутренней перекрестной ссылки). Предоставленный компилятором конструктор копирования разорвет внутренние внутриобъектные ассоциации, преобразовав их в межобъектные ассоциации.
Пример:
struct MarriedMan;
struct MarriedWoman;
struct MarriedMan {
// ...
MarriedWoman* wife; // association
};
struct MarriedWoman {
// ...
MarriedMan* husband; // association
};
struct MarriedCouple {
MarriedWoman wife; // aggregation
MarriedMan husband; // aggregation
MarriedCouple() {
wife.husband = &husband;
husband.wife = &wife;
}
};
MarriedCouple couple1; // couple1.wife and couple1.husband are spouses
MarriedCouple couple2(couple1);
// Are couple2.wife and couple2.husband indeed spouses?
// Why does couple2.wife say that she is married to couple1.husband?
// Why does couple2.husband say that he is married to couple1.wife?
Допускается копирование только объектов, соответствующих определенным критериям
Могут быть классы, в которых объекты можно безопасно копировать, находясь в каком-то состоянии (например, в состоянии по умолчанию), и не , безопасное для копирования в противном случае. Если мы хотим разрешить копирование безопасных для копирования объектов, то - если программирование защищено - нам нужна проверка во время выполнения в определяемом пользователем конструкторе копирования.
Не копируемые подобъекты
Иногда класс, который должен быть копируемым, объединяет не копируемые подобъекты.
Обычно это происходит для объектов с ненаблюдаемым состоянием (этот случай более подробно обсуждается в разделе «Оптимизация» ниже). Компилятор просто помогает распознать этот случай.
Квази-копируемые подобъекты
Класс, который должен быть копируемым, может объединять подобъект квази-копируемого типа. Квази-копируемый тип не предоставляет конструктор копирования в строгом смысле, но имеет другой конструктор, который позволяет создавать концептуальную копию объекта. Причина создания квази-копируемого типа заключается в том, что нет полного согласия относительно семантики копирования типа.
Например, возвращаясь к случаю самостоятельной регистрации объекта, мы можем утверждать, что
могут быть ситуации, когда объект должен быть зарегистрирован в глобальном
Диспетчер объектов, только если это полный автономный объект. Если это
подобъект другого объекта, тогда ответственность за управление им лежит на
содержащий его объект.
Или должно поддерживаться как мелкое, так и глубокое копирование (ни одно из них не является значением по умолчанию).
Затем окончательное решение остается за пользователями этого типа - при копировании объектов они должны явно указать (через дополнительные аргументы) предполагаемый метод копирования.
В случае незащитного подхода к программированию, также возможно, что присутствуют как обычный конструктор копирования, так и конструктор квази-копирования. Это может быть оправдано, когда в подавляющем большинстве случаев следует применять один метод копирования, а в редких, но хорошо понятных ситуациях следует использовать альтернативные методы копирования. Тогда компилятор не будет жаловаться, что не может неявно определить конструктор копирования; только пользователи будут помнить и проверять, нужно ли копировать подобъект такого типа с помощью квази-копирования-конструктора.
Не копировать состояние, тесно связанное с идентификацией объекта
В редких случаях подмножество наблюдаемого состояния объекта может составлять (или считаться) неотделимой частью идентичности объекта и не должно передаваться другим объектам (хотя это может быть несколько спорным).
Примеры:
UID объекта (но этот также относится к случаю "саморегистрации" сверху, поскольку идентификатор должен быть получен в акте саморегистрации).
История объекта (например, стек Undo / Redo) в случае, когда новый объект не должен наследовать историю исходного объекта, а вместо этого начинать с одного элемента истории " Скопировано в ".
В таких случаях конструктор копирования должен пропускать копирование соответствующих подобъектов.
Обеспечение правильной подписи конструктора копирования
Сигнатура предоставленного компилятором конструктора копирования зависит от того, какие конструкторы копирования доступны для подобъектов. Если хотя бы один подобъект не имеет конструктора реальной копии (с исходным объектом по постоянной ссылке), но вместо этого имеет мутирующий конструктор копирования (с исходным объектом (непостоянная ссылка), тогда у компилятора не останется иного выбора, кроме как неявно объявить, а затем определить мутирующий конструктор копирования.
Теперь, что, если «мутирующий» конструктор копирования типа подобъекта на самом деле не мутирует исходный объект (и был просто написан программистом, который не знает о ключевом слове const
)? Если мы не можем исправить этот код, добавив отсутствующий const
, тогда другой вариант - объявить наш собственный определяемый пользователем конструктор копирования с правильной подписью и совершить грех обращения к const_cast
.
Копирование при записи (COW)
Контейнер COW, который передал прямые ссылки на свои внутренние данные, ДОЛЖЕН быть глубоко скопирован во время создания, иначе он может вести себя как дескриптор подсчета ссылок.
Хотя COW - это метод оптимизации, эта логика в конструкторе копирования
имеет решающее значение для его правильной реализации. Вот почему я поместил этот случай здесь
а не в разделе «Оптимизация», куда мы идем дальше.
Оптимизация
В следующих случаях вам может потребоваться / необходимо определить свой собственный конструктор копирования из соображений оптимизации:
Оптимизация структуры при копировании
Рассмотрим контейнер, который поддерживает операции удаления элемента, но может сделать это, просто пометив удаленный элемент как удаленный, и позже перезапустит его слот. Когда создается копия такого контейнера, возможно, имеет смысл сжать сохранившиеся данные, а не сохранять «удаленные» слоты как есть.
Пропустить копирование ненаблюдаемого состояния
Объект может содержать данные, которые не являются частью его наблюдаемого состояния. Обычно это кэшированные / запоминаемые данные, накопленные за время существования объекта, чтобы ускорить некоторые медленные операции запроса, выполняемые объектом. Можно безопасно пропустить копирование этих данных, поскольку они будут пересчитаны, когда (и если!) Будут выполнены соответствующие операции. Копирование этих данных может быть неоправданным, так как оно может быть быстро признано недействительным, если наблюдаемое состояние объекта (из которого получены кэшированные данные) изменяется с помощью операций мутации (и если мы не собираемся изменять объект, почему мы создаем глубокое скопировать потом?)
Эта оптимизация оправдана, только если вспомогательные данные велики по сравнению с данными, представляющими наблюдаемое состояние.
Отключить неявное копирование
C ++ позволяет отключить неявное копирование, объявив конструктор копирования explicit
. Тогда объекты этого класса не могут быть переданы в функции и / или возвращены из функций по значению. Этот прием может быть использован для типа, который кажется легковесным, но на самом деле очень дорогим для копирования (хотя лучше сделать его квази-копируемым).
В C ++ 03 объявление конструктора копии требует его определения (конечно, если
ты намеревался его использовать). Следовательно, переходя к такому конструктору копирования просто
обсуждаемая проблема означала, что вы должны были написать тот же код, который
компилятор автоматически сгенерирует для вас.
C ++ 11 и более новые стандарты позволяют объявлять специальные функции-члены (
конструкторы default и copy, оператор копирования-присваивания и
деструктор) с явным запросом на использование реализации по умолчанию
(просто завершите объявление =default
).
Todos
Этот ответ может быть улучшен следующим образом:
- Добавить пример кода
- Иллюстрируйте случай «Объекты с внутренними перекрестными ссылками»
Добавить ссылки