36
класс
Foo
не содержит чисто виртуальных функций. Не
важно, объявили ли вы свой собственный
конструктор копий; если нет, компилятор построит его за вас. Не важно, есть ли в
Foo
другие
пользовательские конструкторы; в отличие от конструкторов без аргументов,
конструктор копий
доступен
всегда.
Строка
Foo f2 = f
выглядит как присваивание из-за присутствия оператора
=
, но на самом деле это
альтернативный вариант вызова конструктора копий. Чтобы понять, чем присваивание отличается от
инициализации, спросите себя: «Был ли объект сконструирован заранее или же его создание является
частью команды?» Если объект уже существует, вы имеете дело с присваиванием. Если он создается на
месте, как в пашем примере, используется конструктор копий.
При вызове функции
Fn()
происходит передача по значению копии
Foo
. Конструктор копий
используется для создания временной копии, существующей лишь во время выполнения
Fn()
. После
этого вызывается деструктор копии, который уничтожает ее.
Вызов функции
Gn()
, вероятно, ошибочен, и хороший компилятор прочитает вам суровую нотацию о
стиле программирования на C++ — что-нибудь вроде:
«Создается временная неконстантная копия — поучись программировать, тупица!» По крайней мере,
со
мной компиляторы обычно поступают именно так.
Проблема заключается в том, что аргумент
передается по ссылке, однако фактический аргумент является константным, а формальный — нет. Все
изменения аргумента внутри
Gn()
вносятся в копию, а не в оригинал.
В создаваемом компилятором конструкторе копий по умолчанию используется строго определенная
последовательность вызова конструкторов копий базовых классов и переменных класса.
1. Конструкторы копий базовых классов вызываются в том порядке, в котором они объявлены в
списке наследования.
2. Конструкторы копий переменных вызываются в том порядке, в
котором они объявлены в
объявлении класса.
Описанный порядок применяется рекурсивно, то есть первым копируется первый базовый класс
первого базового класса... и т. д. Звучит знакомо, не правда ли?
Тот же порядок, что и для любого
другого конструктора.
С конструкторами копий, в отличие от всех остальных, компилятор ведет себя гордо и ревниво. Если
вы перегрузите конструктор копий для некоторого класса, компилятор,
фигурально выражаясь,
умывает руки и отправляется домой. При отсутствии явного вызова конструкторов копий базовых
классов и переменных класса в списке инициализации членов вашего собственного конструктора
копий компилятор будет использовать
конструктор без аргументов для инициализации базовых
классов и переменных.
class Foo {...};
class Bar : public Foo {
private:
Foo
f;
public:
Bar(const
Bar&);
};
// Вероятно, ошибка
Bar::Bar(const Bar& b)
{
//
Стоп! Нет списка инициализации членов
//
Будут использованы конструкторы без аргументов
//
базового класса и переменной
}
// Вероятно, ошибки нет
Bar::Bar(const Bar& b) : Foo(b), f(b.f) {...}