Чому конструктори не успадковуються?


33

Мене бентежить, які проблеми можуть бути, якби конструктор успадкував базовий клас. Cpp Primer Plus каже:

Конструктори відрізняються від інших класових методів тим, що вони створюють нові об'єкти, тоді як інші методи викликаються існуючими об'єктами . Це одна з причин, що конструктори не успадковуються . Успадкування означає, що похідний об'єкт може використовувати метод базового класу, але, у випадку конструкторів, об'єкт існує доти, доки конструктор не виконає свою роботу.

Я розумію, конструктор викликається до завершення будівництва об'єкта.

Як це може призвести до проблем, якщо дочірній клас успадковує ( успадковуючи, я маю на увазі, що дочірній клас здатний змінити метод батьківського класу тощо. Не просто звертаючись до методу батьківського класу ) батьківський конструктор?

Я розумію, що немає необхідності чітко викликати конструктор з коду (не про що мені відомо), за винятком створення об'єктів. Вже тоді ви можете це зробити, використовуючи якийсь механізм викликати батьківський конвектор [In cpp, using ::or using member initialiser list, In java using super]. У Java є примусовий виклик, щоб викликати його в 1-му рядку, я розумію, що це спочатку створити батьківський об'єкт, а потім триває побудова дочірнього об'єкта.

Це може її перекрити . Але я не можу придумати ситуації, коли це може створити проблему. Якщо дитина наслідує батьківський конструктор, що може піти не так?

Так це просто утриматися від успадкування непотрібних функцій. Або є більше до цього?


Насправді не швидкість з C ++ 11, але я думаю, що в цьому є деяка форма успадкування конструктора.
янніс

3
Майте на увазі, що все, що ви робите в конструкторах, ви повинні дбати про деструктори.
Маной 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 і string, і той, який бере лише int.


Отже, це припускає, що дочірній клас не буде мати власного конструктора? і успадкований конструктор, очевидно, не знає про дітей-членів та ініціалізації, які потрібно зробити
Suvarna Pattayil

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);
}


Друга інтерпретація або, можливо, додаткове запитання - що робити, якщо конструктори класу Base автоматично були присутні у всіх похідних класах, якщо явно не приховано.

Якщо дитина наслідує батьківський конструктор, що може піти не так?

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

Типовий приклад - прямокутники та квадрати (зауважте, що квадрати та прямокутники, як правило, не замінюються Лісковим, тому це не дуже хороший дизайн, але це підкреслює проблему).

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

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

Якщо Square успадкував двозначне конструктор прямокутника, ви могли б побудувати квадрати з різною висотою та шириною ... Це логічно неправильно, тому ви хочете приховати цей конструктор.


3

Чому конструктори не успадковуються: відповідь напрочуд проста: Конструктор базового класу "будує" базовий клас, а конструктор спадкового класу "будує" спадковий клас. Якщо успадкований клас успадкує конструктор, конструктор спробує створити об'єкт базового класу типу, і ви не зможете "побудувати" об'єкт класу, який передається у спадок.

Який вид перемагає мету закладання класу.


3

Найбільш очевидною проблемою, що дозволяє похідному класу перекрити конструктор базового класу, є те, що розробник похідного класу тепер несе відповідальність за те, як знати, як побудувати його базовий клас. Що відбувається, коли похідний клас не побудує базовий клас належним чином?

Також принцип Ліскова-Заміна більше не застосовуватиметься, оскільки ви більше не можете розраховувати на те, що ваша колекція об'єктів базового класу буде сумісною між собою, оскільки немає гарантії, що базовий клас був побудований належним чином або сумісний з іншими похідними типами.

Це ще більше ускладнюється, коли додається більше 1 рівня успадкування. Тепер ваш похідний клас повинен знати, як побудувати всі базові класи в ланцюзі.

Тоді що станеться, якщо додати новий базовий клас до вершини ієрархії спадкування? Вам доведеться оновити всі похідні конструктори класів.


2

Конструктори принципово відрізняються від інших методів:

  1. Вони генеруються, якщо ви не пишете їх.
  2. Усі конструктори базового класу викликаються неявно, навіть якщо ви не робите це вручну
  3. Ви не називаєте їх явно, але створюючи об'єкти.

То чому вони не передаються у спадок? Проста відповідь: Тому що завжди існує переоцінка, генерована або записана вручну.

Чому кожному класу потрібен конструктор? Це складне питання, і я вважаю, що відповідь залежить від компілятора. Існує таке поняття, як "тривіальний" конструктор, для якого компілятор не зобов'язує його називати, здається. Я думаю, що це найближче до того, що ти маєш на увазі під спадкуванням, але з трьох вищезгаданих причин я думаю, що порівняння конструкторів із звичайними методами насправді не корисне. :)


1

Кожен клас потребує конструктора, навіть типових.
C ++ створить для вас конструктори за замовчуванням, за винятком випадків, якщо ви створюєте спеціалізований конструктор.
У випадку, якщо ваш базовий клас використовує спеціалізований конструктор, вам потрібно буде написати спеціалізований конструктор на похідному класі, навіть якщо обидва є однаковими і з'єднати їх назад.
C ++ 11 дозволяє уникнути дублювання коду на конструкторах, використовуючи :

Class A : public B {
using B:B;
....
  1. Набір конструкторів, що успадковуються, складається з

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


0

Ви можете використовувати:

MyClass() : Base()

Ви запитуєте, чому ви повинні це робити?

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

Як ще ви створили б об'єкт підтипу?


Спасибі. Насправді я намагаюся з’ясувати, чому конструктор не успадковується у дочірньому класі, як інші методи батьківського класу.
Suvarna Pattayil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.