Поскольку я в основном работаю над C#. Спустя некоторое время мне пришлось работать над проектом на C++, и он задавался вопросом, почему C++ не полагается на наследование или интерфейсы для связанных методов, таких как std::vector<>::begin(T, Allocator) и std::list<T, Allocator>::begin() ? В C# это обычно используется для классов со схожим поведением, таких как интерфейс ICollection<T> в List<T> и ReadOnlyCollection<T>.
Возможно, это также похоже на деталь реализации.
В C++ по разным причинам «интерфейсы» используются не так часто, как в C#, поэтому их использование не имеет смысла. Иерархии наследования в любом случае ужасны, поэтому я не уверен, что вы ищете.
В шаблонах C++ используется утиная типизация, поэтому общий интерфейс бессмысленен.
Я считаю, что одной из причин является производительность (избегание виртуальной/динамической диспетчеризации).
В C# это обычно используется для классов с похожим поведением. Как уже упоминалось, в C++ нет смысла реализовывать вещи таким образом, когда существуют шаблоны. Пока тип имеет одинаковые характеристики, типы не могут иметь ничего общего друг с другом, но при этом иметь общий код. Если есть шаблонная функция по типу T, и в функции, которую она вызывает T::begin();, не имеет значения, что такое T, будь то базовый класс какого-то типа или что-то, что вы придумали из воздуха без какого-либо «интерфейса». «Джаз продолжается. Пока у него есть функция begin(), это все, что имеет значение.
Потому что C++ лучше поддерживает универсальное программирование. Иерархия в C# (и Java) — это обходной путь отсутствия такой поддержки.
Кстати, раньше нам приходилось создавать иерархию в C++ перед добавлением шаблонов.
@molbdnilo, когда это было? Я думал, что шаблоны были реализованы (версиями) cfront.
@Caleth • В C++ 3.0 (1991 г.) добавлены шаблоны классов и шаблоны функций. Следующей версией была ISO C++98, в которую было добавлено много нового. Perforce История C++.





C++ на самом деле поддерживает интерфейсы разными способами.
виртуальными функциями. Это классический стиль номинальный подтип и это соответствует подходу C#, который вы цитируете.
C++ поддерживает интерфейсы в классическом смысле, где интерфейс подобен классу с абстрактными функциями. В C++ это будут чисто виртуальные функции. Это эквивалент тому, что предлагают другие языки. Однако это приводит к снижению производительности, поскольку вызов виртуальных функций снижает производительность по сравнению с обычными функциями (которые, кроме того, также могут быть встроены).
Помимо снижения производительности, недостатком является невозможность добавления интерфейсов к существующим типам. (Это относится ко всем языкам с этой функцией, а не только к C++)
по понятиям 1. Это более современный подход к интерфейсу, основанный на структурном подтипировании.
std::vector или std::list не имеют интерфейса на основе виртуальных функций по соображениям производительности. Тем не менее, они следуют соглашениям об именах и концептуальных соглашениях. Оба имеют итераторы (хотя и разного класса, но итераторы) и имеют общие функции, такие как beginendinsert, push_back и так далее. Есть даже std::begin, который может работать со всеми контейнерами.
Структурное подтипирование интерфейсов также используется в языке Go. Он предлагает гораздо большую гибкость и расширяемость. Например, можно добавлять интерфейсы к существующим типам без необходимости изменения существующего типа.
С помощью концепций C++201 теперь также можно явно указать требования к типам (т. е. какие методы они должны поддерживать или, в более общем смысле, какие выражения, которые над ними работают, должны быть допустимыми). Это определяет интерфейсы (или концепции того, как они называются в C++). Но это решает не разработчик типа, а использование типа.
Итак, суть: они не имеют общего (виртуального) базового класса по соображениям производительности. Но они имеют общий интерфейс в смысле структурного подтипирования (через концепции C++).
std::enable_if обеспечивает ту же функциональность. Концепции C++20 намного чище и более «интерфейсны», как в современном смысле.Приложение: Примером, демонстрирующим, что наследование (с которым тесно связано номинальное подтипирование) действительно ограничивает и неадекватно, являются числа. Предположим, у нас есть «комплекс», «float», «int». Согласно обычным правилам, «комплексным» должен быть базовый класс, а все остальные наследуются от него. Для компилируемого языка это уже приведет к большому снижению производительности. Когда у вас фиксированная разрядность, все становится еще сложнее. int32 будет подтипом int64 и float64, но не float32 и т. д. Вместе с целыми числами произвольной длины это становится совершенно запутанным.
В этом случае гораздо лучшим подходом является система структурных типов, в которой правила совместимости определяются на основе свойств типов, а не наследования. То же самое относится и к интерфейсам.
Внесем ясность: концепции C++20 не предлагают здесь никаких новых возможностей, это просто более чистый способ выражения требований к типам по сравнению со старым std::enable_if.
«Это относится ко всем языкам с этой функцией, а не только к C++», на самом деле нет, это не так. Go, в частности, не требует от вас объявлять, что ваш тип реализует некоторый интерфейс. И на самом деле вы можете взломать что-то очень похожее на C++ тем же способом, который вы реализуете std:: function.
@PasserBy Но go не попадает в категорию 1, это категория 2. На самом деле не совсем понимаю, что вы имеете в виду ....
@HolyBlackCat Правда, я добавил сноску. Спасибо
@PasserBy Я имею в виду, что в языках категории 1 у вас всегда есть виртуальные функции под капотом. В языках категории 2 могут быть виртуальные функции (например, go), но это также может быть время компиляции (как в C++). Нет?
Нет, я имею в виду, что вы получаете поведение виртуальной функции без его явного объявления, что вы и получаете с помощью std::function.
Для меня ваш ответ кажется немного вводящим в заблуждение. Особенно часть 2 вообще не воспринимается как определение интерфейса. Он просто реализует своего рода утиную типизацию, что означает, что что-то ведет себя как-то, значит, это что-то. Но это не имеет ничего общего с SFINAE или концепциями. Концепции или SFINAE могут поднять уровень обнаружения ошибок, но ничего не меняют.
@Klaus Python, например, вызывает часть 2 «Протоколы» , что для меня на 100% является определением интерфейса (конечно, в смысле структурного подтипирования). Если я посмотрю на предопределенные требования C++, я увижу EqualityComparable, Swappable, CopyConstructible, что ни больше, ни меньше означает, что тип реализует «интерфейс» EqualityComparable, Swappable, CopyConstructible. Просто потому, что C++ называет это требованием, в моем понимании это просто еще одно название интерфейса.
В общем, применять идею одного языка программирования непосредственно к другому не рекомендуется.