[C++] Почему я должен избегать множественного наследования в C ++?


Answers

Из интервью с Бьярне Страуструпом :

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

Question

Является ли хорошей концепцией использование множественного наследования или я могу делать другие вещи вместо этого?




Вы не должны «избегать» множественного наследования, но вы должны знать о проблемах, которые могут возникнуть, например о «проблема с алмазами» ( http://en.wikipedia.org/wiki/Diamond_problem ) и относиться к тому, что вам дано с осторожностью , как вы должны со всеми силами.




он занимает 4/8 байт на каждый класс. (Один указатель на класс).

Это никогда не будет проблемой, но если в один прекрасный день у вас будет микро-структура данных, которая будет установлена ​​в миллиарды раз, это будет.




Ключевая проблема с MI конкретных объектов заключается в том, что редко у вас есть объект, который законно должен «быть A И быть B», поэтому он редко является правильным решением по логическим причинам. Чаще всего у вас есть объект C, который подчиняется «C может выступать как A или B», чего вы можете достичь через наследование и состав интерфейса. Но не ошибитесь - наследование нескольких интерфейсов по-прежнему является MI, а всего лишь его частью.

Для C ++, в частности, ключевая слабость функции не является реальным СУЩЕСТВОВАНИЕМ множественного наследования, но некоторые конструкции, которые она позволяет, почти всегда искажены. Например, наследуя несколько копий одного и того же объекта, например:

class B : public A, public A {};

порождаем ОПРЕДЕЛЕНИЕ. В переводе на английский это «B - это A и A». Таким образом, даже в человеческом языке существует суровая двусмысленность. Вы имели в виду «B имеет 2 As» или просто «B - это A» ?. Предоставляя такой патологический код и, что еще хуже, он стал примером использования, не помогал C ++, когда дело доходило до того, что он поддерживал эту функцию на языках-преемниках.




См. W: Множественное наследование .

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

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

Множественное наследование на языках с конструкторами стиля C ++ / Java усугубляет проблему наследования конструкторов и цепочки конструкторов, тем самым создавая проблемы обслуживания и расширяемости на этих языках. Объекты в отношениях наследования с сильно различными методами построения трудно реализовать в рамках парадигмы цепочки конструкторов.

Современный способ разрешить это использовать интерфейс (чистый абстрактный класс), такой как интерфейс COM и Java.

Я могу сделать другие вещи вместо этого?

Да, ты можешь. Я собираюсь украсть у GoF .

  • Программа для интерфейса, а не реализация
  • Предпочитают композицию над наследованием






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

Другие люди обратились к вопросу о том, какое множественное наследование не подходит. Но мы видели немало комментариев, которые более или менее подразумевают, что причина этого избежать - это небезопасно. Ну, да и нет.

Как это часто бывает в C ++, если вы следуете основному руководству, вы можете безопасно использовать его, не «постоянно переглядывая свое плечо». Основная идея заключается в том, что вы различаете особый вид определения класса, называемый «mix-in»; class является смешанным, если все его функции-члены являются виртуальными (или чистыми виртуальными). Тогда вам разрешено наследовать от одного основного класса и столько «микширования», сколько вам нравится, но вы должны наследовать mixins с ключевым словом «virtual». например

class CounterMixin {
    int count;
public:
    CounterMixin() : count( 0 ) {}
    virtual ~CounterMixin() {}
    virtual void increment() { count += 1; }
    virtual int getCount() { return count; }
};

class Foo : public Bar, virtual public CounterMixin { ..... };

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

Обратите внимание, что мое использование слова «mix-in» здесь не совпадает с параметризованным классом шаблона (см. Эту ссылку для хорошего объяснения), но я считаю, что это справедливое использование терминологии.

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