C++ Почему std::vector<> и std::list<> не имеют общего базового класса/интерфейса?

Поскольку я в основном работаю над C#. Спустя некоторое время мне пришлось работать над проектом на C++, и он задавался вопросом, почему C++ не полагается на наследование или интерфейсы для связанных методов, таких как std::vector<>::begin(T, Allocator) и std::list<T, Allocator>::begin() ? В C# это обычно используется для классов со схожим поведением, таких как интерфейс ICollection<T> в List<T> и ReadOnlyCollection<T>.

В общем, применять идею одного языка программирования непосредственно к другому не рекомендуется.

user12002570 11.04.2024 12:33

Возможно, это также похоже на деталь реализации.

user12002570 11.04.2024 12:34

В C++ по разным причинам «интерфейсы» используются не так часто, как в C#, поэтому их использование не имеет смысла. Иерархии наследования в любом случае ужасны, поэтому я не уверен, что вы ищете.

Passer By 11.04.2024 12:38

В шаблонах C++ используется утиная типизация, поэтому общий интерфейс бессмысленен.

Marek R 11.04.2024 12:40

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

wohlstad 11.04.2024 12:48

В C# это обычно используется для классов с похожим поведением. Как уже упоминалось, в C++ нет смысла реализовывать вещи таким образом, когда существуют шаблоны. Пока тип имеет одинаковые характеристики, типы не могут иметь ничего общего друг с другом, но при этом иметь общий код. Если есть шаблонная функция по типу T, и в функции, которую она вызывает T::begin();, не имеет значения, что такое T, будь то базовый класс какого-то типа или что-то, что вы придумали из воздуха без какого-либо «интерфейса». «Джаз продолжается. Пока у него есть функция begin(), это все, что имеет значение.

PaulMcKenzie 11.04.2024 12:51

Потому что C++ лучше поддерживает универсальное программирование. Иерархия в C# (и Java) — это обходной путь отсутствия такой поддержки.

molbdnilo 11.04.2024 13:09

Кстати, раньше нам приходилось создавать иерархию в C++ перед добавлением шаблонов.

molbdnilo 11.04.2024 14:17

@molbdnilo, когда это было? Я думал, что шаблоны были реализованы (версиями) cfront.

Caleth 11.04.2024 14:36

@Caleth • В C++ 3.0 (1991 г.) добавлены шаблоны классов и шаблоны функций. Следующей версией была ISO C++98, в которую было добавлено много нового. Perforce История C++.

Eljay 11.04.2024 14:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
120
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

C++ на самом деле поддерживает интерфейсы разными способами.

  1. виртуальными функциями. Это классический стиль номинальный подтип и это соответствует подходу C#, который вы цитируете.

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

    Помимо снижения производительности, недостатком является невозможность добавления интерфейсов к существующим типам. (Это относится ко всем языкам с этой функцией, а не только к C++)

  2. по понятиям 1. Это более современный подход к интерфейсу, основанный на структурном подтипировании.

    std::vector или std::list не имеют интерфейса на основе виртуальных функций по соображениям производительности. Тем не менее, они следуют соглашениям об именах и концептуальных соглашениях. Оба имеют итераторы (хотя и разного класса, но итераторы) и имеют общие функции, такие как beginendinsert, push_back и так далее. Есть даже std::begin, который может работать со всеми контейнерами.

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

    С помощью концепций C++201 теперь также можно явно указать требования к типам (т. е. какие методы они должны поддерживать или, в более общем смысле, какие выражения, которые над ними работают, должны быть допустимыми). Это определяет интерфейсы (или концепции того, как они называются в C++). Но это решает не разработчик типа, а использование типа.

Итак, суть: они не имеют общего (виртуального) базового класса по соображениям производительности. Но они имеют общий интерфейс в смысле структурного подтипирования (через концепции C++).

Примечания

  1. На техническом уровне C++11 std::enable_if обеспечивает ту же функциональность. Концепции C++20 намного чище и более «интерфейсны», как в современном смысле.

Приложение: Примером, демонстрирующим, что наследование (с которым тесно связано номинальное подтипирование) действительно ограничивает и неадекватно, являются числа. Предположим, у нас есть «комплекс», «float», «int». Согласно обычным правилам, «комплексным» должен быть базовый класс, а все остальные наследуются от него. Для компилируемого языка это уже приведет к большому снижению производительности. Когда у вас фиксированная разрядность, все становится еще сложнее. int32 будет подтипом int64 и float64, но не float32 и т. д. Вместе с целыми числами произвольной длины это становится совершенно запутанным.

В этом случае гораздо лучшим подходом является система структурных типов, в которой правила совместимости определяются на основе свойств типов, а не наследования. То же самое относится и к интерфейсам.

Внесем ясность: концепции C++20 не предлагают здесь никаких новых возможностей, это просто более чистый способ выражения требований к типам по сравнению со старым std::enable_if.

HolyBlackCat 11.04.2024 13:07

«Это относится ко всем языкам с этой функцией, а не только к C++», на самом деле нет, это не так. Go, в частности, не требует от вас объявлять, что ваш тип реализует некоторый интерфейс. И на самом деле вы можете взломать что-то очень похожее на C++ тем же способом, который вы реализуете std:: function.

Passer By 11.04.2024 13:17

@PasserBy Но go не попадает в категорию 1, это категория 2. На самом деле не совсем понимаю, что вы имеете в виду ....

Andreas H. 11.04.2024 13:43

@HolyBlackCat Правда, я добавил сноску. Спасибо

Andreas H. 11.04.2024 13:44

@PasserBy Я имею в виду, что в языках категории 1 у вас всегда есть виртуальные функции под капотом. В языках категории 2 могут быть виртуальные функции (например, go), но это также может быть время компиляции (как в C++). Нет?

Andreas H. 11.04.2024 13:46

Нет, я имею в виду, что вы получаете поведение виртуальной функции без его явного объявления, что вы и получаете с помощью std::function.

Passer By 11.04.2024 14:10

Для меня ваш ответ кажется немного вводящим в заблуждение. Особенно часть 2 вообще не воспринимается как определение интерфейса. Он просто реализует своего рода утиную типизацию, что означает, что что-то ведет себя как-то, значит, это что-то. Но это не имеет ничего общего с SFINAE или концепциями. Концепции или SFINAE могут поднять уровень обнаружения ошибок, но ничего не меняют.

Klaus 11.04.2024 14:35

@Klaus Python, например, вызывает часть 2 «Протоколы» , что для меня на 100% является определением интерфейса (конечно, в смысле структурного подтипирования). Если я посмотрю на предопределенные требования C++, я увижу EqualityComparable, Swappable, CopyConstructible, что ни больше, ни меньше означает, что тип реализует «интерфейс» EqualityComparable, Swappable, CopyConstructible. Просто потому, что C++ называет это требованием, в моем понимании это просто еще одно название интерфейса.

Andreas H. 11.04.2024 19:31

Другие вопросы по теме