Список инициализаторов элементов конструктора не может включать членов суперкласса?

#c #constructor

#c #конструктор

Вопрос:

Я программирую на C десятилетиями и никогда не ожидал такого ограничения.

Есть ли что-то очевидное, что я упускаю из виду? Я думаю, мне нужно заставить X() конструктор принимать аргумент для i и позволить этому конструктору инициализировать его? В чем может быть смысл этого странного ограничения? Я могу понять запрет на инициализацию объекта дважды, но он вообще не инициализируется! (Или, я полагаю, в отсутствие явного конструктора, конструктор X предоставит ему инициализацию по умолчанию?) И все же:

 class X {
public:
  X() {};
  int i;
};



class X1 : public X {
public:
  X1() : i(1){};
};
  

приводит к ошибке:

 initial.cpp: In constructor 'X1::X1()':
initial.cpp:11:10: error: class 'X1' does not have any field named 'i'
     X1() : i(1){};
            ^
  

Комментарии:

1. Инициализация X таких членов, как i принадлежит X . В противном случае вы потенциально можете инициализировать элемент несколько раз. Компилятор не может определить, действительно ли он будет инициализирован несколько раз или нет. И если решение состоит в том, чтобы просто предоставить X конструктор, я сомневаюсь, что кто-нибудь потрудится изучить возможность его изменения.

2. Построение an X1 всегда вызывает конструктор X , независимо от того, закодирован он явно или нет. В этом случае конструктор по умолчанию X (который не принимает никаких аргументов) неявно вызывается конструктором по умолчанию X1 , и он (в коде yoru) инициализируется по умолчанию X::i . Этот конструктор выполняет роль инициализации X членов s. Итак, если X1 бы конструктору s было каким-то образом разрешено инициализировать X::i напрямую, результатом логически была бы инициализация X::i дважды.

3. @Peter Но каковы недостатки, если мы инициализируем X::i дважды? В некоторых случаях все в порядке

4. @WoooHaaaa это аннулировало бы всю идею «порядка инициализации» и все, что полагается на это в определениях языка. В этом случае это ничего не дает, но что, если «i» — это сложный объект, который создает другие объекты, имеет побочные эффекты, права собственности и т. Д.? «Некоторые случаи» превращаются в целую повестку об исключениях в пределах 300 страниц языковых правил, если это разрешено делать описанным выше способом. Если вас не волнуют затраты, просто сделайте X1() { i = 1; }; . Идеологически неверно, но разрешено, если i является общедоступным или защищенным.

5. @WoooHaaaa — В некоторых случаях это может быть нормально. Существует множество случаев, когда этого не происходит, например, когда инициализация X::i имеет побочные эффекты. В любом случае семантически каждый объект (включая элемент объекта) инициализируется только один раз. Изменение этого, чтобы разрешить конкретные случаи, когда множественная инициализация не имеет значения, значительно усложнило бы правила, связанные с порядком инициализации, без особых преимуществ.

Ответ №1:

Это означает, что поскольку инициализация объекта является частью создания этих объектов, вам либо нужно обеспечить инициализацию через X, либо перепроектировать, чтобы иметь возможность инициализировать его напрямую:

 class X {
public:
  X() {};
  X(int _i) : i(_i) {};
  int i;
};
  

или

 class X {
public:
  int i;
};
  

итак, вы можете сделать это:

 class X1 : public X {
public:
  X1() : X{1}{};
};
  

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

Обратите внимание, что обычно композиция предпочтительнее наследования, если вы нацелены на область хранения, а не на поведение, т.е.

 InterfaceX {
// declarations to be used in descendants
};

class X1 : public InterfaceX
{
  X   m_x;
public:
  X1() : m_x{1} {};
};
  

Если нам действительно требуется полиморфное использование, мы бы использовали InterfaceX для виртуальных методов. И если нам нужно будет «сократить» X1 до X, должен быть оператор преобразования operator X () {return m_x; }