Хотя множественное наследование можно было бы оправдать, апеллируя к некоторым философским аспектам ООП (например, указывая на то, что существует множество устройств, которые можно законно считать как «телефонами», так и «компьютерами» при любом разумном определении этих терминов), когда в частности, речь идет о C++, и здесь можно привести гораздо более сильный аргумент.
Наследование — это не просто философская конструкция; это механизм, предоставляемый языком программирования. И языковые механизмы могут использоваться для решения различных задач. В частности, наследование в C++ — практически единственный способ для класса внедрить в него методы и функциональные возможности извне.
Например, допустим, у вас есть такая конструкция, как std::shared_ptr<T>. Это класс шаблона, который представляет право собственности на T от нескольких владельцев. Одна вещь, которая иногда может быть полезна, — это возможность преобразовать указатель/ссылку на T, которым владеет shared_ptr<T>, в shared_ptr. То есть кто-то дал вам указатель/ссылку, и теперь вы хотите заявить (совместное) право собственности с помощью этого указателя/ссылки.
Теперь, если это сработает, T явно нужно знать, что это возможно, поскольку для этого вам нужно вызвать интерфейсную функцию T. Но T нужно больше, чем просто знать, что это возможно. Когда создается shared_ptr<T>, он создает блок хранения, который управляет временем жизни объекта. Чтобы у T был интерфейс, чтобы претендовать на владение собой, он должен иметь доступ к этому блоку хранилища. Это означает, что конструктор shared_ptr должен подключиться к T, вокруг которого он обертывается, чтобы он мог поместить какой-то указатель на этот блок хранения.
Итак... как это работает? Просто: наследовать от базового класса. Конструктор shared_ptr может использовать базовое метапрограммирование, чтобы определить, является ли T производным от этого базового класса, а затем использовать частный API, чтобы делать с ним свои дела. В частности, тип T должен наследоваться от std::enable_shared_from_this<T>.
И да, я не ошибся: T должен наследоваться от базового класса, созданного по шаблону самого T. Действительно, этот трюк (наследование от базового класса, созданного по шаблону на основе имени производного класса) настолько распространен, что у него есть название: любопытно повторяющийся шаблон шаблона.
Это механизм для реализации формы функциональности миксина в C++. Вы можете связать общую функциональность с базовым классом, и с помощью магии шаблонов вы можете использовать имя производного класса (несмотря на то, что оно еще не определено должным образом) определенными способами, которые позволяют вам вызывать функции производного класса из функций базового класса. .
Это сделано потому, что наследование — единственный инструмент C++ для воздействия на интерфейс члена класса извне класса.
Однако вот в чем дело. Нет никаких причин, по которым enable_shared_from_this<T> не может сосуществовать с некоторыми другими функциями базового класса CRTP. Нет никаких причин, по которым тип не может включить общий доступ из этого и включить некоторые другие функции. Пока примеси не конфликтуют с точки зрения интерфейсов, все в порядке, потому что в основном никто не использует непосредственно сам базовый класс.
Без множественного наследования использование нескольких примесей CRTP было бы невозможно.
Прикрепляю к посту несколько видео по теме: