2010-03-05 3 views
16

Скажем, у меня есть класс, где единственным элементом данных является что-то вроде std::string или std::vector. Должен ли я предоставить команду «Копировать конструктор», «Деструктор» и «Назначение»?При каких обстоятельствах я должен предоставить оператор присваивания, конструктор копирования и деструктор для моего класса C++?

ответ

10

Если ваш класс содержит только векторные/строковые объекты в качестве своих членов данных, вам не нужно их реализовывать. Классы C++ STL (например, вектор, строка) имеют собственный экземпляр ctor, перегруженный оператор присваивания и деструктор.

Но если ваш класс динамически распределяет память в конструкторе, наивная мелкая копия приведет к проблемам. В этом случае вам придется реализовать copy ctor, перегруженный оператор присваивания и деструктор.

0

вам необходимо предоставить их, если они вам понадобятся. или возможных пользователей ваших классов. destructor всегда должен, а конструкторы копирования и оператор присваивания автоматически создаются компилятором. (По меньшей мере, MSVC)

+1

Деструктор также является автоматическим (компилятор не сделает их * виртуальными *, но это еще одна проблема). – visitor

5

Обычное эмпирическое правило гласит: если вам нужен один из них, вам нужны все они.

Не все классы нуждаются в них. Если в классе нет ресурсов (в частности, памяти), с ними все будет в порядке. Например, класс с одним компонентом string или vector им действительно не нужен - если вам не требуется какое-то особое поведение при копировании (по умолчанию будет просто скопировать элементы).

+0

Вместо того, чтобы говорить «не все классы нуждаются в них», не было бы более точным сказать: «Сохранение конструктора копии по умолчанию, деструктора и оператора присваивания будет в порядке».? (То есть вам не нужно будет переопределять значения по умолчанию с вашими собственными реализациями.) – DavidRR

4

Конструктор копирования по умолчанию копирует вектор, если он объявлен по значению. Остерегайтесь, если вы сохранили указатели в своем векторе, в таком случае вам необходимо указать конкретное поведение для копирования/присвоения/уничтожения, чтобы избежать утечек памяти или нескольких удалений.

1

Этим контейнерам понадобится элемент «copy constructible», и если вы не предоставите конструктор копирования, он выберет конструктор копии по умолчанию вашего класса, выведя его из ваших членов класса (мелкая копия).

легко объяснение о конструкторе копирования по умолчанию здесь: http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html

это так с деструктором, контейнер должен иметь доступ к деструктору или вашему деструктору класса по умолчанию, если вы не обеспечиваете один (т.е. он будет. не работает, если вы объявляете своего деструктора личным)

+0

Обнаружена информация по предоставленной ссылке очень полезна. – DavidRR

0

Когда у вас есть класс, требующий глубоких копий, вы должны определить их.

В частности, любой класс, который содержит указатели или ссылки должен содержать их, такие как:

class foo { 
private: 
    int a,b; 
    bar *c; 
} 

Субъективно, я бы сказал, всегда определить их, как поведение по умолчанию, предоставленном компилятором версии не может быть то, что вы ожидание/хочет.

+1

Может быть, лучше сказать: если класс * владеет * ресурсом. Как бы то ни было, этот 'bar' instance' c' указывает на то, что он может принадлежать и контролироваться в другом месте, а 'foo' является просто пользователем этого объекта. - Интересно, что я также рекомендовал бы * not * определять их, если по умолчанию все в порядке: вы гораздо чаще допускаете ошибки, чем компилятор, и прерываете копирование и назначение (и в деструкторе вам нечего делать в первую очередь в таком случае). – visitor

+0

@visitor: см. Ответ lilburne - это в основном то же самое, но более подробно по его причинам - субъективно, я чувствую, что он прав на деньги. –

+0

Естественно, они вам нужны, если вы хотите что-либо за пределами мелкой, по порядку копии. Но я не совсем уверен, почему вы должны сделать это вручную для членской копии (что является для меня большинством классов, если они должны быть скопированы в первую очередь) - если это не то, что вы ожидаете, возможно, вы ожидать очень странную семантику от копирования. - Возможно, объективная причина вручную писать оператор присваивания - это значит, что вы можете дать более надежные гарантии (lhv не изменился, а не просто пропущена утечка памяти), но я полагаю, что это было бы очень сложно (нужно отменить изменения), которые должны выполняться повсеместно. – visitor

0

Не для строк или векторов, так как тривиальные конструкторы/деструкторы и т. Д. Сделают все возможное.

Если ваш класс имеет указатели на другие данные и нуждается в глубоких копиях, или если ваш класс содержит ресурс, который должен быть освобожден или должен быть скопирован особым образом.

2

Нет, но существует ряд причин, по которым вы не должны позволять компилятору автоматически генерировать эти функции.

По моему опыту, всегда лучше определить их самостоятельно и привыкнуть к тому, чтобы они поддерживались при изменении класса. Во-первых, вы можете захотеть поставить точку останова при вызове определенного ctor или dtor. Кроме того, их определение не может привести к разрыву кода, поскольку компилятор будет генерировать встроенные вызовы членам ctor и dtor (у Скотта Мейерса есть раздел об этом).

Также вы иногда хотите запретить копии и задания копирования по умолчанию. Например, у меня есть приложение, которое хранит и обрабатывает очень большие блоки данных. У нас обычно есть эквивалент вектора STL, содержащего миллионы 3D-точек, и это было бы катастрофой, если бы мы разрешили копирование этих контейнеров. Таким образом, операторы ctor и присваивания объявляются частными и не определены. Таким образом, если кто-нибудь напишет

class myClass { 
    void doSomething(const bigDataContainer data); // not should be passed by reference 
} 

тогда они получат ошибку компилятора. Наш опыт заключается в том, что явный метод get() или clone() гораздо менее подвержен ошибкам.

Таким образом, у всех есть много причин, чтобы избежать автоматических генерируемых функций компилятора.

+0

должен 'не' быть' заметкой'? – Bill

+1

«привыкайте к тому, чтобы они поддерживались при изменении класса». Это лишний кошмар для обслуживания. – fredoverflow

+0

Если у вас нет модульных тестов для ваших ctors и т. Д., Чтобы проверить правильность инициализации? Если вы не рассматриваете все последствия добавления членов данных в классы? Если вы добавляете новую строку в класс, каково влияние на раздувание кода на всех используемых им методах и на всех классах, которые могут содержать экземпляры? Добавив нового члена, вам не нужно пересматривать вопрос о том, является ли возможность автогенерации более жизнеспособной? Пока вы задаетесь вопросом обо всех тех вещах, которые добавляются в copy-ctor, а op = минимально. – lilburne

2

Я могу придумать несколько случаев, когда вам нужно написать собственную большую тройку. Все стандартные контейнеры умеют копировать и уничтожать самих себя, поэтому вам не обязательно их писать. Вот как это знать:

Имеет ли в моем классе какие-либо ресурсы?

семантика копирования по умолчанию для указателей, чтобы скопировать значениеуказателя, а не то, что он указывает. Если вам нужно что-то глубоко скопировать, даже если оно хранится в стандартном контейнере, вам нужно написать собственный конструктор копий и оператор присваивания. Вам также нужно написать собственный деструктор, чтобы правильно освободить эти ресурсы.

Возможно, кто-то наследует мой класс?

Базовые классы нуждаются в деструкторе. Herb Sutter рекомендует сделать их либо public, либо virtual (наиболее распространенный случай) или protected и не виртуальными, в зависимости от того, что вы хотите с ними делать. Деструктор, созданный компилятором, является открытым и не виртуальным, поэтому вам придется писать свои собственные, даже если в нем нет никакого кода. (Примечание: это не означает, что вам нужно написать оператор конструктор копирования или присваивания.)

Должен ли я запретить пользователю копировать объекты моего класса?

Если вы не хотите, чтобы пользователь, чтобы скопировать ваши объекты (возможно, что это слишком дорого), вы должны объявить конструктор копирования и оператор присваивания либо protected или private. Вам не нужно их реализовывать, если они вам не нужны. (Примечание: это не означает, что вам нужно написать деструктор.)

Итог:

Самое главное, чтобы понять, что сгенерированный компилятором конструктор копирования, оператор присваивания и деструктор будет делать. Вам не нужно их бояться, но вам нужно подумать о них и решить, подходит ли их поведение для вашего класса.