Я знаю, что компилятор C++ создает конструктора копии для класса. В этом случае мы должны записать пользовательскому конструктору копии? Можно ли дать некоторые примеры?
Конструктор копирования, создаваемый компилятором, выполняет копирование по членам. Иногда этого недостаточно. Например:
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;
}
Конструктор копирования вызывается, когда объект передается по значению, возвращается по значению или явно копируется. Если конструктора копирования нет, C ++ создает конструктор копирования по умолчанию, который создает неглубокую копию. Если у объекта нет указателей на динамически выделяемую память, подойдет неглубокая копия.
Меня немного раздражает, что не было приведено правило Правило пяти
.
Это правило очень простое:
Правило пяти:
Всякий раз, когда вы пишете один из Destructor, Copy Constructor, Copy Assignment Operator, Move Constructor или Move Assignment Operator, вам, вероятно, нужно написать остальные четыре.
Но есть и более общее правило, которому вы должны следовать, вытекающее из необходимости писать безопасный от исключений код:
Каждый ресурс должен управляться выделенным объектом
Здесь @sharptooth
код все еще (в основном) в порядке, но если бы он добавил второй атрибут к своему классу, это было бы не так. Рассмотрим следующий класс:
class Erroneous
{
public:
Erroneous();
// ... others
private:
Foo* mFoo;
Bar* mBar;
};
Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}
Что происходит, если new Bar
бросается? Как вы удалите объект, на который указывает mFoo
? Есть решения (try/catch на уровне функций ...), но они просто не масштабируются.
Правильный способ справиться с ситуацией - использовать соответствующие классы вместо сырых указателей.
class Righteous
{
public:
private:
std::unique_ptr<Foo> mFoo;
std::unique_ptr<Bar> mBar;
};
С той же реализацией конструктора (или фактически, используя make_unique
), я теперь имею безопасность исключений бесплатно!!! Разве это не здорово? И самое главное, мне больше не нужно беспокоиться о правильном деструкторе! Правда, мне придется написать свои собственные Copy Constructor
и Assignment Operator
, потому что unique_ptr
не определяет эти операции... но это здесь не важно ;)
И поэтому, sharptooth
's class revisited:
class Class
{
public:
Class(char const* str): mData(str) {}
private:
std::string mData;
};
Не знаю как вам, а мне мой проще ;)
Часто рекомендуется отключить ctor и operator = для копирования, если это специально не требуется классу. Это может предотвратить неэффективность, например передачу аргумента по значению, когда предполагается ссылка. Также методы, созданные компилятором, могут быть недопустимыми.
Если у вас есть класс с динамически распределяемым содержимым. Например, вы храните название книги как char * и устанавливаете название с помощью new, копирование не будет работать.
Вам придется написать конструктор копирования, который делает title = new char[length+1]
, а затем strcpy(title, titleIn)
. Конструктор копирования будет просто выполнять "неглубокое" копирование.