Почему C ++ не позволяет производному классу использовать член базового класса в списке инициализации?

У меня есть следующая программа:

#include<iostream>
using namespace std;
struct Base01{
int m;
Base01():m(2){}
void p(){cout<<m<<endl;}
};
struct Derived01:public Base01{
Derived01():m(3){}
};
struct Derived02:virtual public Base01{
Derived01():m(4){}
};
struct my: Derived01,Derived02{
my():m(5){}
};
int main(){
return 0;
}

Оба gcc / clang сообщают об ошибке компиляции.

Я просто хочу знать, что здесь подразумевается при проектировании языка, почему производный класс может вызывать только базовый класс ctor в списке инициализации, но не может напрямую использовать членов базового класса?

2

Решение

Что вы делаете в списке инициализатора конструктора инициализация. Это то, что должно быть сделано только однажды в жизни объекта. В общем случае вот что запускает объекты продолжительность жизни.

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

В вашем случае рассматриваемый подобъект имеет фундаментальный тип int, поэтому трудно увидеть вред в такой «реинициализации». Но рассмотрим что-то менее тривиальное, как std::string объект. Как, по вашему мнению, производный класс должен «отменить и повторить» инициализацию, уже выполненную базовым классом? И хотя формально это можно сделать правильно, списки инициализатора конструктора не предназначены для этой цели.

В общем случае, чтобы сделать что-то подобное, потребовалась бы языковая особенность, которая позволяла бы пользователю сказать конструктору базового класса что-то вроде: «Пожалуйста, оставьте этот субобъект неинициализированным, я доберусь до него и инициализирую его позже из производного класса» , Однако C ++ не предоставляет пользователям такую ​​возможность. При инициализации виртуального базового класса существует неопределенно похожая функция, но она служит очень специфической (и другой) цели.

8

Другие решения

Правильный способ сделать это в C ++ — передать это значение конструктору базового класса. Ваш Base01 Класс нуждается в дополнительном конструкторе, который принимает желаемое значение для m. Что-то вроде этого:

struct Base01{
int m;
Base01():m(2){}

// Added this:
Base01(int mVal) : m(mVal) {}

void p(){cout<<m<<endl;}
};

struct Derived01:public Base01{
Derived01() : Base01(3) {}   // Calling base constructor rather than
// initializing base member
};

struct Derived02:virtual public Base01{
Derived01() : Base01(4){}    // Same here
};

struct my: Derived01,Derived02{
my(): Base01(5){}            // And here.
};

Как сказал AnT, вы не можете инициализировать дважды — но вы можете настроить его так, чтобы все инициализировалось так, как вы хотите, в первую очередь, выполнив, как описано выше.

3

Вы, конечно, можете использование член базового класса в списке инициализатора ctor:

struct Base
{
int x;
Base(int x) : x(x) {}
};

struct Derived
{
int y;
Derived() : Base(7), y(x) {}
}

Здесь базовый член x появляется в инициализаторе для производного члена y; его значение будет использовано.

AnT проделала отличную работу, объясняя, почему список ctor-initializer нельзя использовать для (повторной) инициализации членов базовых подобъектов.

2

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

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

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

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

Более простое средство — для базового класса предоставить конструктор, который правильно инициализировал рассматриваемый член, и для конструктора производного класса вызвать этот конструктор базового класса в своем списке инициализатора. Все вышеперечисленные (гипотетические) соображения затем исчезнут.

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector