15.7.2. Синтезируемые функции управления копированием и наследование

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

• Синтезируемый стандартный конструктор класса Bulk_quote запускает стандартный конструктор класса Disc_quote, который в свою очередь запускает стандартный конструктор класса Quote.

• Стандартный конструктор класса Quote инициализирует по умолчанию переменную-член bookNo пустой строкой и использует внутриклассовый инициализатор для инициализации переменной-члена price нулем.

• Когда конструктор класса Quote завершает работу, конструктор класса Disc_quote продолжает ее, используя внутриклассовые инициализаторы для инициализации переменных qty и discount. 

• Когда завершает работу конструктор класса Disc_quote, конструктор класса Bulk_quote продолжает ее, но не выполняет никаких других действий.

Точно так же синтезируемый конструктор копий класса Bulk_quote использует (синтезируемый) конструктор копий класса Disc_quote, который использует (синтезируемый) конструктор копий класса Quote. Конструктор копий класса Quote копирует переменные-члены bookNo и price; а конструктор копий класса Disc_quote копирует переменные-члены qty и discount.

Следует заметить, что не имеет значения, синтезируется ли функция-член базового класса (как в случае иерархии Quote) или имеет предоставленное пользователем определение. Важно лишь то, что соответствующая функция-член доступна (см. раздел 15.5) и что она не удаленная.

Каждый из классов иерархии Quote использует синтезируемый деструктор. Производные классы делают это неявно, тогда как класс Quote делает это явно, определяя свой (виртуальный) деструктор как = default. Синтезируемый деструктор (как обычно) пуст, и его неявная часть удаляет члены класса (см. раздел 13.1.3). В дополнение к удалению собственных членов фаза удаления деструктора в производном классе удаляет также свою прямую базовую часть. Этот деструктор в свою очередь вызывает деструктор своего прямого базового класса, если он есть. И так далее до корневого класса иерархии.

Как уже упоминалось, у класса Quote нет синтезируемых функций перемещения, поскольку он определяет деструктор. При каждом перемещении объекта Quote (см. раздел 13.6.2) будут использоваться (синтезируемые) функции копирования. Как будет продемонстрировано ниже, тот факт, что у класса Quote нет функций перемещения, означает, что его производные классы также не будут их иметь.

Базовые классы и удаленные функции управления копированием в производном классе

Синтезируемый стандартный конструктор или любая из функций-членов управления копированием базового либо производного класса может быть определена как удаленная по тем же причинам, что и в любом другом классе (см. раздел 13.1.6 и раздел 13.6.2). Кроме того, способ определения базового класса может вынудить член производного класса стать удаленным.

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

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

• Как обычно, компилятор не будет синтезировать удаленную функцию перемещения. Если использовать синтаксис = default для создания функции перемещения, то это будет удаленная функция в производном классе, если соответствующая функция в базовом классе будет удалена или недоступна, поскольку часть базового класса не может быть перемещена. Конструктор перемещения также будет удален, если деструктор базового класса окажется удален или недоступен.

Для примера рассмотрим базовый класс В:

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, если вообще повлияет, удаление этого конструктора?

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК