C ++ неявный конструктор копирования для класса, который содержит другие объекты - PullRequest
46 голосов
/ 27 ноября 2009

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

class Foo {
  Bar bar;
};

class Bar {
  int i;
  Baz baz;
};

class Baz {
  int j;
};

Теперь, если я сделаю это:

Foo f1;
Foo f2(f1);

Что будет делать конструктор копирования по умолчанию? Будет ли сгенерированный компилятором конструктор копирования в Foo вызывать сгенерированный компилятором конструктор в Bar для копирования bar, который затем вызовет сгенерированный компилятором конструктор копирования в Baz?

Ответы [ 5 ]

67 голосов
/ 27 ноября 2009
Foo f1;
Foo f2(f1);

Да, это будет делать то, что вы ожидаете:
Вызывается конструктор копирования f2 Foo :: Foo (Foo const &).
Эта копия создает свой базовый класс, а затем каждый член (рекурсивно)

Если вы определяете класс следующим образом:

class X: public Y
{
    private:
        int     m_a;
        char*   m_b;
        Z       m_c;
};

Следующие методы будут определены вашим компилятором.

  • Конструктор (по умолчанию) (2 версии)
  • Конструктор (Копия)
  • Деструктор (по умолчанию)
  • Оператор присваивания

Конструктор: по умолчанию:

На самом деле есть два конструктора по умолчанию.
Один используется для zero-initialization, а другой - для value-initialization. Используемое зависит от того, используете ли вы () во время инициализации или нет.

// Zero-Initialization compiler generated constructor
X::X()
    :Y()                // Calls the base constructor
                        //     If this is compiler generated use 
                        //     the `Zero-Initialization version'
    ,m_a(0)             // Default construction of basic PODS zeros them
    ,m_b(0)             // 
    m_c()               // Calls the default constructor of Z
                        //     If this is compiler generated use 
                        //     the `Zero-Initialization version'
{
}

// Value-Initialization compiler generated constructor
X::X()
    :Y()                // Calls the base constructor
                        //     If this is compiler generated use 
                        //     the `Value-Initialization version'
    //,m_a()            // Default construction of basic PODS does nothing
    //,m_b()            // The values are un-initialized.
    m_c()               // Calls the default constructor of Z
                        //     If this is compiler generated use 
                        //     the `Value-Initialization version'
{
}

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

Конструктор (Копия)

X::X(X const& copy)
    :Y(copy)            // Calls the base copy constructor
    ,m_a(copy.m_a)      // Calls each members copy constructor
    ,m_b(copy.m_b)
    ,m_c(copy.m_c)
{}

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

Оператор присваивания

X& operator=(X const& copy)
{
    Y::operator=(copy); // Calls the base assignment operator
    m_a = copy.m_a;     // Calls each members assignment operator
    m_b = copy.m_b;
    m_c = copy.m_c;

    return *this;
}

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

Destructor

X::~X()
{
                        // First runs the destructor code
}
    // This is psudo code.
    // But the equiv of this code happens in every destructor
    m_c.~Z();           // Calls the destructor for each member
    // m_b              // PODs and pointers destructors do nothing
    // m_a          
    ~Y();               // Call the base class destructor
  • Если объявлен любой конструктор (включая копию), то конструктор по умолчанию не реализован компилятором.
  • Если конструктор копирования объявлен, компилятор его не сгенерирует.
  • Если оператор присваивания объявлен, компилятор его не сгенерирует.
  • Если объявлен деструктор, компилятор его не сгенерирует.

Глядя на ваш код, генерируются следующие конструкторы копирования:

Foo::Foo(Foo const& copy)
    :bar(copy.bar)
{}

Bar::Bar(Bar const& copy)
    :i(copy.i)
    ,baz(copy.baz)
{}

Baz::Baz(Baz const& copy)
    :j(copy.j)
{}
11 голосов
/ 27 ноября 2009

Компилятор предоставляет конструктор копирования, если вы не объявите (примечание: не определите ). Генерируемый компилятором конструктор копирования просто вызывает конструктор копирования каждого члена класса (и каждого базового класса).

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

2 голосов
/ 27 ноября 2009

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

0 голосов
/ 27 ноября 2009

Конструктор копирования по умолчанию для C ++ создает мелкую копию. Мелкая копия не будет создавать новые копии объектов, на которые может ссылаться ваш исходный объект; старые и новые объекты будут просто содержать разные указатели на одну и ту же ячейку памяти.

0 голосов
/ 27 ноября 2009

Компилятор сгенерирует нужные вам конструкторы.

Однако, как только вы сами определите конструктор копирования, компилятор прекратит генерацию чего-либо для этого класса и выдаст ошибку, если у вас не определены соответствующие конструкторы.

Используя ваш пример:

class Baz {
    Baz(const Baz& b) {}
    int j;
};
class Bar {
    int i;
    Baz baz;
};
class Foo {
    Bar bar;
};

Попытка создать экземпляр по умолчанию или создать копию Foo приведет к ошибке, поскольку Baz не может быть создан для копирования, и компилятор не может сгенерировать конструктор по умолчанию и конструктор копирования для Foo.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...