C++ неявный конструктор копирования для класса, содержащего другие объекты

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

class Foo {
  Bar bar;
};

class Bar {
  int i;
  Baz baz;
};

class Baz {
  int j;
};

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

Foo f1;
Foo f2(f1);

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

5 ответов


Foo f1;
Foo f2(f1);

Да, это будет делать то, что вы ожидаете:
В Ф2 копия конструктор 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;
}

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

деструктор

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)
{}

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

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


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


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


компилятор создаст необходимые конструкторы для вас.

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

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

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

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