Я знаю, что компилятор C ++ создает конструктор копирования для класса. В каком случае нам нужно написать определяемый пользователем конструктор копирования? Вы можете привести несколько примеров?
c++
copy-constructor
Penguru
источник
источник
Ответы:
Конструктор копирования, созданный компилятором, выполняет поэлементное копирование. Иногда этого недостаточно. Например:
class Class { public: Class( const char* str ); ~Class(); private: char* stored; }; Class::Class( const char* str ) { stored = new char[srtlen( str ) + 1 ]; strcpy( stored, str ); } Class::~Class() { delete[] stored; }
в этом случае поэлементное копирование
stored
члена не будет дублировать буфер (будет скопирован только указатель), поэтому первая уничтожаемая копия, совместно использующая буфер, вызоветdelete[]
успешно, а вторая будет работать с неопределенным поведением. Вам нужен конструктор копии с глубоким копированием (а также оператор присваивания).Class::Class( const Class& another ) { stored = new char[strlen(another.stored) + 1]; strcpy( stored, another.stored ); } void Class::operator = ( const Class& another ) { char* temp = new char[strlen(another.stored) + 1]; strcpy( temp, another.stored); delete[] stored; stored = temp; }
источник
delete stored[];
и я считаю, что она должна бытьdelete [] stored;
std::string
. Общая идея состоит в том, что только служебные классы, которые управляют ресурсами, должны перегружать «большую тройку», а все остальные классы должны просто использовать эти служебные классы, устраняя необходимость определять какой-либо из «большой тройки».Меня немного раздражает, что правило
Rule of Five
не процитировано.Это правило очень простое:
Но есть более общее правило, которому вы должны следовать, которое вытекает из необходимости писать безопасный код исключений:
Здесь
@sharptooth
код все еще (в основном) в порядке, однако, если бы он добавил второй атрибут в свой класс, этого бы не произошло. Рассмотрим следующий класс:class Erroneous { public: Erroneous(); // ... others private: Foo* mFoo; Bar* mBar; }; Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}
Что будет, если
new Bar
бросит? Как удалить объект, на который указываетmFoo
? Есть решения (на уровне функций попробуй / поймай ...), они просто не масштабируются.Правильный способ справиться с ситуацией - использовать правильные классы вместо необработанных указателей.
class Righteous { public: private: std::unique_ptr<Foo> mFoo; std::unique_ptr<Bar> mBar; };
С той же реализацией конструктора (или фактически с использованием
make_unique
) у меня теперь есть безопасность исключений бесплатно !!! Разве это не интересно? И, что самое главное, мне больше не нужно беспокоиться о правильном деструкторе! Мне нужно написать свое собственноеCopy Constructor
иAssignment Operator
хотя, потомуunique_ptr
что не определяет эти операции ... но здесь это не имеет значения;)И поэтому
sharptooth
снова посетил класс пользователя:class Class { public: Class(char const* str): mData(str) {} private: std::string mData; };
Не знаю, как вы, но мне легче;)
источник
Я могу вспомнить из своей практики и подумать о следующих случаях, когда приходится иметь дело с явным объявлением / определением конструктора копирования. Я сгруппировал дела в две категории
Правильность / семантика
Я помещаю в этот раздел случаи, когда объявление / определение конструктора копирования необходимо для правильной работы программ, использующих этот тип.
Прочитав этот раздел, вы узнаете о нескольких подводных камнях, позволяющих компилятору самостоятельно сгенерировать конструктор копирования. Поэтому, как заметил в своем ответе seand , всегда безопасно отключить возможность копирования для нового класса и намеренно включить ее позже, когда это действительно необходимо.
Как сделать класс не копируемым в C ++ 03
Объявите частный копирующий конструктор и не предоставляйте для него реализацию (чтобы сборка завершилась ошибкой на этапе связывания, даже если объекты этого типа копируются в собственную область видимости класса или его друзьями).
Как сделать класс не копируемым в C ++ 11 или новее
Объявите конструктор-копию с помощью
=delete
at end.Мелкая и глубокая копия
Это наиболее понятный случай и фактически единственный, упомянутый в других ответах. shaprtooth была покрыта его довольно хорошо. Я только хочу добавить, что глубоко копируемые ресурсы, которые должны принадлежать исключительно объекту, могут применяться к любому типу ресурсов, из которых динамически выделяемая память - это только один вид. При необходимости глубокое копирование объекта может также потребовать
Саморегистрирующиеся объекты
Рассмотрим класс, в котором все объекты - независимо от того, как они были созданы - ДОЛЖНЫ быть каким-то образом зарегистрированы. Некоторые примеры:
Самый простой пример: ведение общего количества существующих на данный момент объектов. Регистрация объекта - это просто увеличение статического счетчика.
Более сложным примером является одноэлементный реестр, в котором хранятся ссылки на все существующие объекты этого типа (так что уведомления могут быть доставлены им всем).
Умные указатели с подсчетом ссылок можно рассматривать как особый случай в этой категории: новый указатель «регистрирует» себя в общем ресурсе, а не в глобальном реестре.
Такая операция саморегистрации должна выполняться ЛЮБЫМ конструктором данного типа, и конструктор копирования не является исключением.
Объекты с внутренними перекрестными ссылками
Некоторые объекты могут иметь нетривиальную внутреннюю структуру с прямыми перекрестными ссылками между их различными подобъектами (фактически, для запуска этого случая достаточно только одной такой внутренней перекрестной ссылки). Предоставленный компилятором конструктор копирования нарушит внутренние внутриобъектные ассоциации, преобразовав их в межобъектные ассоциации.
Пример:
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) в случае, когда новый объект не должен наследовать историю исходного объекта, а вместо этого должен начинаться с одного элемента истории « Скопировано в <TIME> из <OTHER_OBJECT_ID> ».
В таких случаях конструктор копирования должен пропустить копирование соответствующих подобъектов.
Обеспечение правильной подписи конструктора копирования
Подпись предоставленного компилятором конструктора копирования зависит от того, какие конструкторы копирования доступны для подобъектов. Если хотя бы один подобъект не имеет реального конструктора копии (принимающего исходный объект по постоянной ссылке), но вместо этого имеет изменяющийся копирующий конструктор (принимающий исходный объект по непостоянной ссылке), то у компилятора не будет выбора. но неявно объявить, а затем определить изменяющийся копирующий конструктор.
А что, если «мутирующий» конструктор-копию типа подобъекта фактически не изменяет исходный объект (и был просто написан программистом, который не знает о
const
ключевом слове)? Если мы не можем исправить этот код, добавив недостающийconst
, то другой вариант - объявить наш собственный определяемый пользователем конструктор копирования с правильной подписью и совершить грех обращения к файлуconst_cast
.Копирование при записи (COW)
Контейнер COW, который дал прямые ссылки на свои внутренние данные, ДОЛЖЕН быть глубоко скопирован во время создания, иначе он может вести себя как дескриптор подсчета ссылок.
Оптимизация
В следующих случаях вам может потребоваться / необходимо определить собственный конструктор копирования из соображений оптимизации:
Оптимизация структуры при копировании
Рассмотрим контейнер, который поддерживает операции удаления элементов, но может сделать это, просто пометив удаленный элемент как удаленный, а затем повторно использовать его слот. Когда создается копия такого контейнера, может иметь смысл сжать уцелевшие данные, а не сохранять «удаленные» слоты как есть.
Пропустить копирование ненаблюдаемого состояния
Объект может содержать данные, которые не являются частью его наблюдаемого состояния. Обычно это кэшированные / запомненные данные, накопленные за время существования объекта, чтобы ускорить некоторые медленные операции запроса, выполняемые объектом. Можно безопасно пропустить копирование этих данных, поскольку они будут пересчитаны, когда (и если!) Будут выполнены соответствующие операции. Копирование этих данных может быть неоправданным, так как оно может быть быстро признано недействительным, если наблюдаемое состояние объекта (из которого извлекаются кэшированные данные) изменено операциями изменения (и если мы не собираемся изменять объект, почему мы создаем глубокий копировать тогда?)
Эта оптимизация оправдана только в том случае, если вспомогательные данные велики по сравнению с данными, представляющими наблюдаемое состояние.
Отключить неявное копирование
C ++ позволяет отключить неявное копирование, объявив конструктор копирования
explicit
. Тогда объекты этого класса не могут быть переданы в функции и / или возвращены из функций по значению. Этот трюк можно использовать для типа, который кажется легковесным, но действительно очень дорогим для копирования (хотя сделать его квазикопируемым может быть лучшим выбором).TODOs
источник
Если у вас есть класс с динамически выделяемым содержимым. Например, вы сохраняете название книги как символ * и устанавливаете заголовок с новым, копировать не будет.
Вам нужно будет написать конструктор копирования, который делает,
title = new char[length+1]
а затемstrcpy(title, titleIn)
. Конструктор копирования просто сделает «неглубокую» копию.источник
Конструктор копирования вызывается, когда объект либо передается по значению, возвращается по значению, либо явно копируется. Если конструктора копирования нет, C ++ создает конструктор копирования по умолчанию, который делает неглубокую копию. Если у объекта нет указателей на динамически выделяемую память, подойдет неглубокая копия.
источник
Часто рекомендуется отключить копирование ctor и operator =, если это не требуется классу. Это может предотвратить неэффективность, например передачу аргумента по значению, когда предполагается ссылка. Также методы, созданные компилятором, могут быть недопустимыми.
источник
Рассмотрим ниже фрагмент кода:
class base{ int a, *p; public: base(){ p = new int; } void SetData(int, int); void ShowData(); base(const base& old_ref){ //No coding present. } }; void base :: ShowData(){ cout<<this->a<<" "<<*(this->p)<<endl; } void base :: SetData(int a, int b){ this->a = a; *(this->p) = b; } int main(void) { base b1; b1.SetData(2, 3); b1.ShowData(); base b2 = b1; //!! Copy constructor called. b2.ShowData(); return 0; }
Output: 2 3 //b1.ShowData(); 1996774332 1205913761 //b2.ShowData();
b2.ShowData();
дает нежелательный вывод, потому что существует определенный пользователем конструктор копирования, созданный без кода, написанного для явного копирования данных. Таким образом, компилятор не создает то же самое.Просто подумал о том, чтобы поделиться этими знаниями со всеми вами, хотя большинство из вас это уже знают.
Ура ... Удачного кодирования !!!
источник