Скажем, у меня есть класс, где единственным элементом данных является что-то вроде std::string
или std::vector
. Должен ли я предоставить команду «Копировать конструктор», «Деструктор» и «Назначение»?При каких обстоятельствах я должен предоставить оператор присваивания, конструктор копирования и деструктор для моего класса C++?
ответ
Если ваш класс содержит только векторные/строковые объекты в качестве своих членов данных, вам не нужно их реализовывать. Классы C++ STL (например, вектор, строка) имеют собственный экземпляр ctor, перегруженный оператор присваивания и деструктор.
Но если ваш класс динамически распределяет память в конструкторе, наивная мелкая копия приведет к проблемам. В этом случае вам придется реализовать copy ctor, перегруженный оператор присваивания и деструктор.
вам необходимо предоставить их, если они вам понадобятся. или возможных пользователей ваших классов. destructor всегда должен, а конструкторы копирования и оператор присваивания автоматически создаются компилятором. (По меньшей мере, MSVC)
Обычное эмпирическое правило гласит: если вам нужен один из них, вам нужны все они.
Не все классы нуждаются в них. Если в классе нет ресурсов (в частности, памяти), с ними все будет в порядке. Например, класс с одним компонентом string
или vector
им действительно не нужен - если вам не требуется какое-то особое поведение при копировании (по умолчанию будет просто скопировать элементы).
Вместо того, чтобы говорить «не все классы нуждаются в них», не было бы более точным сказать: «Сохранение конструктора копии по умолчанию, деструктора и оператора присваивания будет в порядке».? (То есть вам не нужно будет переопределять значения по умолчанию с вашими собственными реализациями.) – DavidRR
Конструктор копирования по умолчанию копирует вектор, если он объявлен по значению. Остерегайтесь, если вы сохранили указатели в своем векторе, в таком случае вам необходимо указать конкретное поведение для копирования/присвоения/уничтожения, чтобы избежать утечек памяти или нескольких удалений.
Этим контейнерам понадобится элемент «copy constructible», и если вы не предоставите конструктор копирования, он выберет конструктор копии по умолчанию вашего класса, выведя его из ваших членов класса (мелкая копия).
легко объяснение о конструкторе копирования по умолчанию здесь: http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html
это так с деструктором, контейнер должен иметь доступ к деструктору или вашему деструктору класса по умолчанию, если вы не обеспечиваете один (т.е. он будет. не работает, если вы объявляете своего деструктора личным)
Обнаружена информация по предоставленной ссылке очень полезна. – DavidRR
Когда у вас есть класс, требующий глубоких копий, вы должны определить их.
В частности, любой класс, который содержит указатели или ссылки должен содержать их, такие как:
class foo {
private:
int a,b;
bar *c;
}
Субъективно, я бы сказал, всегда определить их, как поведение по умолчанию, предоставленном компилятором версии не может быть то, что вы ожидание/хочет.
Может быть, лучше сказать: если класс * владеет * ресурсом. Как бы то ни было, этот 'bar' instance' c' указывает на то, что он может принадлежать и контролироваться в другом месте, а 'foo' является просто пользователем этого объекта. - Интересно, что я также рекомендовал бы * not * определять их, если по умолчанию все в порядке: вы гораздо чаще допускаете ошибки, чем компилятор, и прерываете копирование и назначение (и в деструкторе вам нечего делать в первую очередь в таком случае). – visitor
@visitor: см. Ответ lilburne - это в основном то же самое, но более подробно по его причинам - субъективно, я чувствую, что он прав на деньги. –
Естественно, они вам нужны, если вы хотите что-либо за пределами мелкой, по порядку копии. Но я не совсем уверен, почему вы должны сделать это вручную для членской копии (что является для меня большинством классов, если они должны быть скопированы в первую очередь) - если это не то, что вы ожидаете, возможно, вы ожидать очень странную семантику от копирования. - Возможно, объективная причина вручную писать оператор присваивания - это значит, что вы можете дать более надежные гарантии (lhv не изменился, а не просто пропущена утечка памяти), но я полагаю, что это было бы очень сложно (нужно отменить изменения), которые должны выполняться повсеместно. – visitor
Не для строк или векторов, так как тривиальные конструкторы/деструкторы и т. Д. Сделают все возможное.
Если ваш класс имеет указатели на другие данные и нуждается в глубоких копиях, или если ваш класс содержит ресурс, который должен быть освобожден или должен быть скопирован особым образом.
Нет, но существует ряд причин, по которым вы не должны позволять компилятору автоматически генерировать эти функции.
По моему опыту, всегда лучше определить их самостоятельно и привыкнуть к тому, чтобы они поддерживались при изменении класса. Во-первых, вы можете захотеть поставить точку останова при вызове определенного ctor или dtor. Кроме того, их определение не может привести к разрыву кода, поскольку компилятор будет генерировать встроенные вызовы членам ctor и dtor (у Скотта Мейерса есть раздел об этом).
Также вы иногда хотите запретить копии и задания копирования по умолчанию. Например, у меня есть приложение, которое хранит и обрабатывает очень большие блоки данных. У нас обычно есть эквивалент вектора STL, содержащего миллионы 3D-точек, и это было бы катастрофой, если бы мы разрешили копирование этих контейнеров. Таким образом, операторы ctor и присваивания объявляются частными и не определены. Таким образом, если кто-нибудь напишет
class myClass {
void doSomething(const bigDataContainer data); // not should be passed by reference
}
тогда они получат ошибку компилятора. Наш опыт заключается в том, что явный метод get() или clone() гораздо менее подвержен ошибкам.
Таким образом, у всех есть много причин, чтобы избежать автоматических генерируемых функций компилятора.
должен 'не' быть' заметкой'? – Bill
«привыкайте к тому, чтобы они поддерживались при изменении класса». Это лишний кошмар для обслуживания. – fredoverflow
Если у вас нет модульных тестов для ваших ctors и т. Д., Чтобы проверить правильность инициализации? Если вы не рассматриваете все последствия добавления членов данных в классы? Если вы добавляете новую строку в класс, каково влияние на раздувание кода на всех используемых им методах и на всех классах, которые могут содержать экземпляры? Добавив нового члена, вам не нужно пересматривать вопрос о том, является ли возможность автогенерации более жизнеспособной? Пока вы задаетесь вопросом обо всех тех вещах, которые добавляются в copy-ctor, а op = минимально. – lilburne
Я могу придумать несколько случаев, когда вам нужно написать собственную большую тройку. Все стандартные контейнеры умеют копировать и уничтожать самих себя, поэтому вам не обязательно их писать. Вот как это знать:
Имеет ли в моем классе какие-либо ресурсы?
семантика копирования по умолчанию для указателей, чтобы скопировать значениеуказателя, а не то, что он указывает. Если вам нужно что-то глубоко скопировать, даже если оно хранится в стандартном контейнере, вам нужно написать собственный конструктор копий и оператор присваивания. Вам также нужно написать собственный деструктор, чтобы правильно освободить эти ресурсы.
Возможно, кто-то наследует мой класс?
Базовые классы нуждаются в деструкторе. Herb Sutter рекомендует сделать их либо public
, либо virtual
(наиболее распространенный случай) или protected
и не виртуальными, в зависимости от того, что вы хотите с ними делать. Деструктор, созданный компилятором, является открытым и не виртуальным, поэтому вам придется писать свои собственные, даже если в нем нет никакого кода. (Примечание: это не означает, что вам нужно написать оператор конструктор копирования или присваивания.)
Должен ли я запретить пользователю копировать объекты моего класса?
Если вы не хотите, чтобы пользователь, чтобы скопировать ваши объекты (возможно, что это слишком дорого), вы должны объявить конструктор копирования и оператор присваивания либо protected
или private
. Вам не нужно их реализовывать, если они вам не нужны. (Примечание: это не означает, что вам нужно написать деструктор.)
Итог:
Самое главное, чтобы понять, что сгенерированный компилятором конструктор копирования, оператор присваивания и деструктор будет делать. Вам не нужно их бояться, но вам нужно подумать о них и решить, подходит ли их поведение для вашего класса.
Деструктор также является автоматическим (компилятор не сделает их * виртуальными *, но это еще одна проблема). – visitor