Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
class B {
public:
B();
B(const B&) = delete;
// другие члены, исключая конструктор перемещения
};
class D : public B {
// нет конструкторов
};
D d; // ok: синтезируемый стандартный конструктор класса D использует
// стандартный конструктор класса В
D d2(d); // ошибка: синтезируемый конструктор копий класса D удален
D d3(std::move(d)); // ошибка: неявно использованный удаленный
// конструктор копий класса D
Класс имеет доступный стандартный конструктор и явно удаленный конструктор копий. Поскольку конструктор копий определяется, компилятор не будет синтезировать для класса В конструктор перемещения (см. раздел 13.6.2). В результате невозможно ни переместить, ни скопировать объекты типа В. Если бы класс, производный от типа В, хотел позволить своим объектам копирование или перемещение, то этот производный класс должен был бы определить свои собственные версии этих конструкторов. Конечно, этот класс должен был бы решить, как скопировать или переместить члены в эту часть базового класса. Практически, если у базового класса нет стандартного конструктора копий или конструктора перемещения, то его производные классы также обычно не будут их иметь.
Функции перемещения и наследованиеКак уже упоминалось, большинство базовых классов определяет виртуальный деструктор. В результате по умолчанию базовые классы вообще не получают синтезируемых функций перемещения. Кроме того, по умолчанию классы, производные от базового класса, у которого нет функций перемещения, также не получают синтезируемых функций перемещения.
Поскольку отсутствие функции перемещения в базовом классе подавляет синтез функций перемещения в его производных классах, базовые классы обычно должны определять функции перемещения, если это имеет смысл. Наш класс Quote может использовать синтезируемые версии. Однако класс Quote должен определить эти члены явно. Как только он определит собственные функции перемещения, он должен будет также явно определить версии копирования (см. раздел 13.6.2):
class Quote {
public:
Quote() = default; // почленная инициализация по умолчанию
Quote(const Quote&) = default; // почленное копирование
Quote(Quote&&) = default; // почленное копирование
Quote& operator=(const Quote&) = default; // присвоение копии
Quote& operator=(Quotes&) = default; // перемещение
virtual ~Quote() = default;
// другие члены, как прежде
};
Теперь объекты класса Quote будут почленно копироваться, перемещаться, присваиваться и удаляться. Кроме того, классы, производные от класса Quote, также автоматически получат синтезируемые функции перемещения, если у них не будет членов, которые воспрепятствуют перемещению.
Упражнения раздела 15.7.2Упражнение 15.25. Зачем определять стандартный конструктор для класса Disc_quote? Как повлияет на поведение класса Bulk_quote, если вообще повлияет, удаление этого конструктора?
15.7.3. Функции-члены управления копированием производного класса
Как упоминалось в разделе 15.2.2, фаза инициализации конструктора производного класса инициализирует часть (части) базового класса производного объекта наряду с инициализацией его собственных членов. В результате конструкторы копирования и перемещения для производного класса должны копировать и перемещать члены своей базовой части наравне с производной. Точно так же оператор присвоения производного класса должен присваивать члены базовой части производного объекта.
В отличие от конструкторов и операторов присвоения, деструктор несет ответственность только за освобождение ресурсов, зарезервированных производным классом. Помните, что члены объекта освобождаются неявно (см. раздел 13.1.3). Точно так же часть базового класса объекта производного класса освобождается автоматически.
Когда производный класс определяет функцию копирования или перемещения, эта функция несет ответственность за копирование или перемещение всего объекта, включая члены базового класса.
Определение конструктора копии или перемещения производного классаПри определении конструктора копии или перемещения (см. раздел 13.1.1 и раздел 13.6.2) для производного класса обычно используется соответствующий конструктор базового класса, инициализирующий базовую часть объекта:
class Base { /* ... */ };
class D: public Base {
public:
// по умолчанию стандартный конструктор базового класса
// инициализирует базовую часть объекта
// чтобы использовать конструктор копии или перемещения, его следует
// вызвать явно
// конструктор в списке инициализации конструктора
D(const D& d) : Base(d) // копирование базовых членов
/* инициализаторы для членов класса D */ { /* ... */ }
D(D&& d): Base(std::move(d)) // перемещение базовых членов
/* инициализаторы для членов класса D */ { /* ... */ }
};
Инициализатор Base(d) передает объект класса D конструктору базового класса. Хотя в принципе у класса Base может быть конструктор с параметром типа D, на практике это очень маловероятно. Вместо этого инициализатор Base(d) будет (обычно) соответствовать конструктору копий класса Base. В этом конструкторе объект d будет связан с параметром типа Base&. Конструктор копий класса Base скопирует базовую часть объекта d в создаваемый объект. Будь инициализатор для базового класса пропущен, для инициализации базовой части объекта класса D будет использован стандартный конструктор класса Base.
// вероятно, неправильное определение конструктора копий D
// часть базового класса инициализируется по умолчанию, а не копией
D(const D& d) /* инициализаторы членов класса, но не базового класса */
{ /* ... */ }
Предположим, что конструктор класса D копирует производные члены объекта d. Этот вновь созданный объект был бы настроен странно: его члены класса Base содержали бы значения по умолчанию, в то время как его члены класса D были бы копиями данных из другого объекта.
По умолчанию стандартный конструктор базового класса инициализирует часть базового класса объекта производного. Если необходимо копирование (или перемещение) части базового класса, следует явно использовать конструктор копий (или перемещения) для базового класса в списке инициализации конструктора производного.
Оператор присвоения производного классаПодобно конструктору копирования и перемещения, оператор присвоения производного класса (см. раздел 13.1.2 и раздел 13.6.2) должен присваивать свою базовую часть явно:
// Base::operator=(const Base&) не вызывается автоматически
D &D::operator=(const D &rhs) {
Base::operator=(rhs); // присваивает базовую часть
// присвоение членов в производном классе, как обычно,
// отработка самоприсвоения и освобождения ресурсов
return *this;
}
Этот оператор начинается с явного вызова оператора присвоения базового класса, чтобы присвоить члены базовой части объекта производного. Оператор базового класса (по-видимому, правильно) отработает случай присвоения себя себе и, если нужно, освободит прежнее значение в базовой части левого операнда и присвоит новое значение правой. По завершении работы оператора продолжается выполнение всего необходимого для присвоения членов в производном классе.
Следует заметить, что конструктор или оператор присвоения производного класса может использовать соответствующую функцию базового класса независимо от того, определил ли базовый класс собственную версию этого оператора или использует синтезируемую. Например, вызов оператора Base::operator= выполняет оператор присвоения копии в классе Base. При этом несущественно, определяется ли этот оператор классом Base явно или синтезируется компилятором.
Деструктор производного классаПомните, переменные-члены объекта неявно удаляются после завершения выполнения тела деструктора (см. раздел 13.1.3). Точно так же части базового класса объекта тоже удаляются неявно. В результате, в отличие от конструкторов и операторов присвоения, производный деструктор отвечает за освобождение только тех ресурсов, которые зарезервировал производный класс: