Почему конструкторы не наследуются?

33

Я не понимаю, какие могут быть проблемы, если конструктор был унаследован от базового класса. Cpp Primer Plus говорит,

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

Я понимаю, что конструктор вызывается до завершения строительства объекта.

Как это может привести к проблемам, если дочерний класс наследует (под наследованием я подразумеваю, что дочерний класс может переопределять метод родительского класса и т. Д. Не просто получая доступ к методу родительского класса ) родительский конструктор?

Я понимаю, что нет необходимости явно вызывать конструктор из кода [не то, что я знаю пока.] Кроме как при создании объектов. Даже тогда вы можете сделать это, используя некоторый механизм для вызова родительского конструктора [В cpp, using ::или using member initialiser list, В java using super]. В Java есть обязательство вызывать его в 1-й строке, я понимаю, что сначала нужно создать родительский объект, а затем продолжить конструирование дочернего объекта.

Это может переопределить это. Но я не могу придумать ситуации, когда это может создать проблему. Если ребенок наследует родительский конструктор, что может пойти не так?

Так что это просто, чтобы избежать наследования ненужных функций. Или есть что-то еще?

Суварна Паттайил
источник
Не совсем в курсе C ++ 11, но я думаю, что в нем есть какая-то форма наследования конструктора.
Яннис
3
Имейте в виду, что что бы вы ни делали в конструкторах, вы должны заботиться о деструкторах.
Manoj R
2
Я думаю, что вам нужно определить, что подразумевается под «наследованием конструктора». Все ответы, кажется, имеют различия в том, что, по их мнению, означает «наследование конструктора». Я не думаю, что кто-то из них соответствует моей первоначальной интерпретации. Описание «в сером поле» сверху «Наследование означает, что производный объект может использовать метод базового класса», подразумевает, что конструкторы наследуются. Производный объект наверняка может и использует методы конструктора базового класса, ВСЕГДА.
Данк
1
Спасибо за разъяснение о наследовании конструктора, теперь вы можете получить некоторые ответы.
Данк

Ответы:

41

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

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

В C ++ 11 была введена форма «наследования конструктора», где вы можете дать указание компилятору сгенерировать для вас набор конструкторов, которые принимают те же аргументы, что и конструкторы из базового класса, и просто передают эти аргументы в Базовый класс.
Хотя это официально называется наследованием, на самом деле это не так, потому что есть функция, специфичная для производного класса. Теперь он просто генерируется компилятором, а не явно написан вами.

Эта функция работает так:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedтеперь имеет два конструктора (не считая конструкторов копирования / перемещения). Тот, который принимает int и строку, и тот, который принимает только int.

Барт ван Инген Шенау
источник
Так что это предполагает, что дочерний класс не будет иметь своего собственного конструктора? и унаследованный конструктор, очевидно, не знает о дочерних элементах и ​​инициализациях, которые необходимо выполнить
Суварна Паттайил
C ++ определяется таким образом, что класс не может иметь своего собственного конструктора (ов). Поэтому я не считаю «конструктор наследования» на самом деле быть наследственность. Это просто способ избежать написания утомительного шаблона.
Барт ван Инген Шенау
2
Я думаю, что путаница проистекает из того факта, что даже пустой конструктор выполняет закулисную работу. Таким образом, конструктор действительно состоит из двух частей: внутренняя часть и часть, которую вы пишете. Поскольку они плохо разделены, конструкторы не наследуются.
Сариен
1
Пожалуйста, рассмотрите возможность указать, откуда m()и как он будет меняться, если, например, его тип int.
Дедупликатор
Можно ли это сделать using Base::Baseкосвенно? Это имело бы большие последствия, если бы я забыл эту строку в производном классе, и мне нужно наследовать конструктор во всех производных классах
Post Self
7

Непонятно, что вы подразумеваете под «наследованием родительского конструктора». Вы используете слово переопределение , которое предполагает, что вы можете подумать о конструкторах, которые ведут себя как полиморфные виртуальные функции . Я намеренно не использую термин «виртуальные конструкторы», потому что это общее имя для шаблона кода, когда вам фактически требуется уже существующий экземпляр объекта для создания другого.

Полиморфные конструкторы мало полезны вне шаблона «виртуального конструктора», и трудно придумать конкретный сценарий, в котором может быть использован реальный полиморфный конструктор. Очень надуманный пример, который никоим образом не является даже удаленно верным C ++ :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

В этом случае вызываемый конструктор зависит от конкретного типа создаваемой или присваиваемой переменной. Его сложно обнаружить во время синтаксического анализа / генерации кода и не имеет никакой полезности: вы знаете конкретный тип, который вы создаете, и вы написали специальный конструктор для производного класса. Следующий действительный код C ++ делает то же самое, немного короче и более явно в том, что он делает:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


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

Если ребенок наследует родительский конструктор, что может пойти не так?

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

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

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Если бы Square унаследовал конструктор Rectangle с двумя значениями, вы могли бы создавать квадраты с разной высотой и шириной ... Это логически неправильно, поэтому вы захотите скрыть этот конструктор.

Йорис Тиммерманс
источник
3

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

Какой вид поражает цель закрепления класса.

Питер Б
источник
3

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

Кроме того, принцип Liskov-Substitution больше не будет применяться, поскольку вы больше не можете рассчитывать на то, что ваша коллекция объектов базового класса будет совместима друг с другом, потому что нет гарантии, что базовый класс был построен правильно или совместим с другими производными типами.

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

Что произойдет, если вы добавите новый базовый класс в начало иерархии наследования? Вы должны обновить все конструкторы производных классов.

Замочить
источник
2

Конструкторы принципиально отличаются от других методов:

  1. Они генерируются, если вы их не пишете.
  2. Все конструкторы базового класса вызываются неявно, даже если вы не делаете это вручную
  3. Вы не вызываете их явно, но создавая объекты.

Так почему они не унаследованы? Простой ответ: потому что всегда есть переопределение, сгенерированное или написанное вручную.

Почему каждому классу нужен конструктор? Это сложный вопрос, и я считаю, что ответ зависит от компилятора. Существует такая вещь, как «тривиальный» конструктор, для которого компилятор не предписывает, что он будет вызван, кажется. Я думаю, что это самое близкое к тому, что вы подразумеваете под наследованием, но по трем причинам, указанным выше, я думаю, что сравнение конструкторов с обычными методами не очень полезно. :)

Sarien
источник
1

Каждому классу нужен конструктор, даже по умолчанию.
C ++ создаст для вас конструкторы по умолчанию, кроме случаев, когда вы создаете специализированный конструктор.
В случае, если ваш базовый класс использует специализированный конструктор, вам нужно написать специализированный конструктор в производном классе, даже если оба они одинаковы, и объединить их в цепочку.
C ++ 11 позволяет избежать дублирования кода на конструкторах, используя :

Class A : public B {
using B:B;
....
  1. Набор наследующих конструкторов состоит из

    • Все не шаблонные конструкторы базового класса (после пропуска параметров многоточия, если они есть) (начиная с C ++ 14)
    • Для каждого конструктора с аргументами по умолчанию или параметром многоточия все сигнатуры конструктора, которые формируются путем удаления многоточия и опускания аргументов по умолчанию из концов списков аргументов, один за другим
    • Все шаблоны конструктора базового класса (после пропуска параметров многоточия, если они есть) (начиная с C ++ 14)
    • Для каждого шаблона конструктора с аргументами по умолчанию или многоточием все сигнатуры конструктора, которые формируются путем удаления многоточия и опускания аргументов по умолчанию из концов списков аргументов, один за другим
  2. Все унаследованные конструкторы, которые не являются конструктором по умолчанию или конструктором копирования / перемещения и подписи которых не соответствуют пользовательским конструкторам в производном классе, неявно объявляются в производном классе. Параметры по умолчанию не наследуются

Meduza
источник
0

Вы можете использовать:

MyClass() : Base()

Вы спрашиваете, почему вы должны это сделать?

Подкласс может иметь дополнительные свойства, которые, возможно, потребуется инициализировать в конструкторе, или он может инициализировать переменные базового класса другим способом.

Как еще вы могли бы создать объект подтипа?

Том Том
источник
Спасибо. На самом деле, я пытаюсь выяснить, почему конструктор не наследуется в дочернем классе, как другие методы родительского класса.
Суварна Паттайил