Почему в C ++ нет базового класса?


84

С точки зрения дизайна, почему в C ++ нет единого базового класса, который обычно используется objectв других языках?


27
На самом деле это скорее философский вопрос, чем вопрос дизайна. Короткий ответ: «потому что Бьярн явно не хотел этого делать».
Джерри Коффин

22
Лично я считаю недостатком дизайна то, что все унаследовано от одного и того же базового класса. Он предлагает стиль программирования, который вызывает больше проблем, чем он того стоит (поскольку у вас обычно есть общие контейнеры объектов, которые вам затем нужно использовать, чтобы использовать фактический объект (это я не одобряю как плохой дизайн, IMHO)). Действительно ли мне нужен контейнер, в котором можно разместить как машины, так и объекты автоматизации управления?
Мартин Йорк

3
@Martin, но посмотрите на это с другой стороны: например, до C ++ 0x auto вам приходилось использовать определения типа длиной в милю для итераторов или одноразовые typedefs. При более общей иерархии классов вы можете просто использовать objectили iterator.
slezica

9
@Santiago: использование унифицированной системы типов почти всегда означает, что вы получаете большой объем кода, основанного на полиморфизме, динамической отправке и RTTI, которые относительно дороги и все это препятствует оптимизации, которая возможна, когда они не используются.
Джеймс МакНеллис

2
@GWW - ... кроме того факта, что он не будет применяться ни к одному из контейнеров STL.
Крис Лутц

Ответы:


116

Окончательное решение можно найти в FAQ Страуструпа . Короче говоря, никакого смыслового значения он не передает. Это будет дорого стоить. Для контейнеров больше подходят шаблоны.

Почему в C ++ нет универсального класса Object?

  • Он нам не нужен: универсальное программирование в большинстве случаев предоставляет альтернативы, безопасные для статического типа. Другие случаи обрабатываются с использованием множественного наследования.

  • Нет никакого полезного универсального класса: действительно универсальный не несет в себе собственной семантики.

  • «Универсальный» класс поощряет небрежное мышление о типах и интерфейсах и приводит к избыточным проверкам во время выполнения.

  • Использование универсального базового класса требует затрат: объекты должны быть распределены в куче, чтобы быть полиморфными; это подразумевает стоимость памяти и доступа. Объекты кучи, естественно, не поддерживают семантику копирования. Объекты кучи не поддерживают простое поведение с заданной областью (что усложняет управление ресурсами). Универсальный базовый класс поощряет использование dynamic_cast и других проверок времени выполнения.


83
Нам не нужен / объект базового класса / нам не нужен / контроль мысли ...;)
Писквор покинул здание

3
@Piskvor - LOL Я хочу посмотреть клип!
Байрон Уитлок

11
Objects must be heap-allocated to be polymorphic- Я не считаю это утверждение в целом правильным. Вы определенно можете создать экземпляр полиморфного класса в стеке и передать его как указатель на один из его базовых классов, что приведет к полиморфному поведению.
Байрон

5
В Java попытка Fooпреобразовать ссылку- на-в ссылку- Barкогда Barне наследуется Foo, детерминированно вызывает исключение. В C ++ попытка преобразовать указатель на Foo в указатель на Bar могла отправить робота назад во времени, чтобы убить Сару Коннор.
supercat

3
@supercat Вот почему мы используем dynamic_cast <>. Чтобы избежать SkyNet и таинственных диванов Честерфилда.
Captain Giraffe

44

Давайте сначала подумаем, зачем вам вообще нужен базовый класс. Я могу придумать несколько разных причин:

  1. Для поддержки общих операций или коллекций, которые будут работать с объектами любого типа.
  2. Включить различные процедуры, общие для всех объектов (например, управление памятью).
  3. Все является объектом (никаких примитивов!). Некоторые языки (например, Objective-C) не имеют этого, что делает вещи довольно запутанными.

Это две веские причины, по которым языки брендов Smalltalk, Ruby и Objective-C имеют базовые классы (технически Objective-C на самом деле не имеет базового класса, но для всех намерений и целей он есть).

Для №1 необходимость в базовом классе, объединяющем все объекты в едином интерфейсе, устраняется включением шаблонов в C ++. Например:

void somethingGeneric(Base);

Derived object;
somethingGeneric(object);

не требуется, если вы можете полностью поддерживать целостность типов с помощью параметрического полиморфизма!

template <class T>
void somethingGeneric(T);

Derived object;
somethingGeneric(object);

Для № 2, в то время как в Objective-C процедуры управления памятью являются частью реализации класса и наследуются от базового класса, управление памятью в C ++ выполняется с использованием композиции, а не наследования. Например, вы можете определить оболочку интеллектуального указателя, которая будет выполнять подсчет ссылок на объекты любого типа:

template <class T>
struct refcounted
{
  refcounted(T* object) : _object(object), _count(0) {}

  T* operator->() { return _object; }
  operator T*() { return _object; }

  void retain() { ++_count; }

  void release()
  {
    if (--_count == 0) { delete _object; }
  }

  private:
    T* _object;
    int _count;
};

Тогда вместо вызова методов самого объекта вы будете вызывать методы в его оболочке. Это не только позволяет более общее программирование: это также позволяет вам разделять проблемы (поскольку в идеале ваш объект должен больше заботиться о том, что он должен делать, а не о том, как его память должна управляться в различных ситуациях).

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

Итак, краткий ответ на ваш вопрос: C ++ не имеет базового класса, потому что, имея параметрический полиморфизм через шаблоны, он не нужен.


Все нормально! Рад, что вы нашли это полезным :)
Джонатан Стерлинг

2
В пункте 3 я использую C #. В C # есть примитивы, которые можно поместить в object( System.Object), но это не обязательно. Для компилятора intи System.Int32являются псевдонимами и могут использоваться как взаимозаменяемые; при необходимости среда выполнения обрабатывает бокс.
Коул Джонсон

В C ++ бокс не нужен. С помощью шаблонов компилятор генерирует правильный код во время компиляции.
Роб К.

2
Обратите внимание, что, хотя этот (старый) ответ демонстрирует умный указатель с подсчетом ссылок, в C ++ 11 теперь есть, std::shared_ptrкоторый следует использовать.

15

Доминирующая парадигма для переменных C ++ - передача по значению, а не по ссылке. Принудительное извлечение всего из корня Objectприведет к передаче их по значению с ошибкой ipse facto.

(Потому что принятие объекта по значению в качестве параметра по определению приведет к его разрезанию и удалению его души).

Это нежелательно. C ++ заставляет задуматься о том, нужна ли вам семантика значений или ссылок, предоставляя вам выбор. Это важная вещь для вычислений производительности.


1
Само по себе это не было бы ошибкой. Иерархии типов уже существуют, и при необходимости они довольно хорошо используют передачу по значению. Однако это будет способствовать использованию стратегий на основе кучи, где передача по ссылке более уместна.
Деннис Зикефуз

IMHO, хороший язык должен иметь разные виды наследования для ситуаций, когда производный класс может законно использоваться в качестве объекта базового класса с использованием среза по ссылке, тех, где его можно превратить в один с помощью среза по значению, и те, где ни то, ни другое не является законным. Ценность наличия общего базового типа для вещей, которые можно нарезать, будет ограничена, но многие вещи нельзя нарезать, и они должны храниться косвенно; дополнительные затраты на то, чтобы такие вещи происходили из общего, Objectбыли бы относительно небольшими.
supercat

5

Проблема в том, что в C ++ такой тип ЕСТЬ! Это так void. :-) Любой указатель можно безопасно неявно привести кvoid * , включая указатели на базовые типы, классы без виртуальной таблицы и классы с виртуальной таблицей.

Поскольку он должен быть совместим со всеми этими категориями объектов, voidсам по себе не может содержать виртуальных методов. Без виртуальных функций и RTTI невозможно получить полезную информацию о типе void(он соответствует КАЖДОМУ типу, поэтому может говорить только то, что верно для КАЖДОГО типа), но виртуальные функции и RTTI сделают простые типы очень неэффективными и не позволят C ++ быть язык, подходящий для низкоуровневого программирования с прямым доступом к памяти и т. д.

Итак, есть такой тип. Он просто предоставляет очень минималистичный (фактически пустой) интерфейс из-за низкоуровневой природы языка. :-)


Типы указателей и типы объектов не совпадают. У вас не может быть объекта типа void.
Кевин Панко

1
Фактически, именно так обычно работает наследование в C ++. Одна из самых важных вещей, которую он выполняет, - это совместимость указателей и ссылочных типов: там, где требуется указатель на класс, может быть указан указатель на производный класс. А возможность статического преобразования указателей между двумя типами является признаком того, что они связаны в иерархию. С этой точки зрения void определенно является универсальным базовым классом в C ++.
Ellioh 05

Если вы объявите переменную типа, voidона не будет компилироваться, и вы получите эту ошибку . В Java у вас может быть переменная типа, Objectи она будет работать. В этом разница между voidтипом и «настоящим». Возможно, это правда, что voidэто базовый тип для всего, но, поскольку он не предоставляет никаких конструкторов, методов или полей, невозможно определить, есть он там или нет. Это утверждение нельзя ни доказать, ни опровергнуть.
Кевин Панко

1
В Java каждая переменная является ссылкой. Вот в чем разница. :-) (Кстати, в C ++ также есть абстрактные классы, которые нельзя использовать для объявления переменной). И в своем ответе я объяснил, почему невозможно получить RTTI из void. Итак, теоретически void по-прежнему является базовым классом для всего. static_cast является доказательством, поскольку он выполняет приведение только между связанными типами, и его можно использовать для void. Но вы правы, отсутствие доступа RTTI в void действительно сильно отличает базовые типы от языков более высокого уровня.
Ellioh

1
@Ellioh Посмотрев на стандарт C ++ 11 §3.9.1p9, я признаю, что voidэто тип (и обнаружил несколько умное использование? : ), но он все еще далек от универсального базового класса. Во-первых, это «неполный тип, который невозможно завершить», а во-вторых, void&тип отсутствует .
bcrist

-2

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

Возьмем, например, узор

template <class T> class Hook;
template <class ReturnType, class ... ArgTypes>
class Hook<ReturnType (ArgTypes...)>
{
   ...
   ReturnType operator () (ArgTypes... args) { ... }
};

который может быть создан как

Hook<decltype(some_function)> ...;

Теперь предположим, что мы хотим того же для конкретной функции. подобно

template <auto fallback> class Hook;
template <auto fallback, class ReturnType, class ... ArgTypes>
class Hook<ReturnType fallback(ArgTypes...)>
{
   ...
   ReturnType operator () (ArgTypes... args) { ... }
};

со специализированным экземпляром

Hook<some_function> ...

Но, увы, даже несмотря на то, что класс T может заменять любой тип (класс или нет) до специализации, нет эквивалента auto fallback(я использую этот синтаксис как наиболее очевидный универсальный нетиповый в данном контексте), который мог бы заменять любой аргумент шаблона без типа перед специализацией.

Таким образом, обычно этот шаблон не переносится из аргументов шаблона типа в аргументы шаблона, не являющиеся типом.

Как и в случае с множеством углов в языке C ++, ответ, вероятно, будет «ни один член комитета не подумал об этом».


Даже если (что-то вроде) ваш ReturnType fallback(ArgTypes...)будет работать, это будет плохой дизайн. template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...делает то, что вы хотите, и означает, что параметры шаблона Hookимеют одинаковые типы
Калет

-4

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

Сам по себе C представляет собой смесь языков, языка программирования C и его препроцессора.

C ++ добавляет еще один микс:

  • подход класса / объекта

  • шаблоны

  • STL

Мне лично не нравятся некоторые вещи, которые идут напрямую с C на C ++. Одним из примеров является функция enum. То, как C # позволяет разработчику использовать его, намного лучше: он ограничивает перечисление в своей собственной области видимости, имеет свойство Count и легко повторяется.

Поскольку C ++ хотел быть ретро-совместимым с C, дизайнер был очень снисходительным, позволив языку C полностью войти в C ++ (есть некоторые тонкие различия, но я не помню ничего, что вы могли бы сделать с помощью компилятора C, который вы не мог использовать компилятор C ++).


«не могу рассматривать C ++ как язык, а как основу языков» ... хотите на самом деле объяснить и проиллюстрировать, к чему ведет это странное утверждение? Множественная парадигма отличается от «множественности языков», хотя справедливо будет сказать, что препроцессор отделен от остальных шагов компилятора - хороший момент. Перечисления - их можно тривиально обернуть в область видимости класса, а всему языку не хватает самоанализа - серьезная проблема, хотя в этом случае ее можно приблизить - см. Код benum Boost vault. Ваша точка зрения только на вопрос: C ++ не начинался с универсальной основы?
Тони Делрой

@Tony: Любопытно, что в книге, на которую я ссылался, единственное, что они даже не упоминают, поскольку другой язык, - это препроцессор, который является единственным, о котором вы рассматриваете отдельно. Я понимаю вашу точку зрения, поскольку препроцессор сначала разбирает свой ввод. Но иметь механизм шаблонов - все равно что иметь другой язык, который делает обобщение за вас. Вы применяете синтаксис шаблона, и у вас будут функции, генерирующие компилятор для каждого типа, который у вас есть. Когда вы можете написать программу на C и передать ее компилятору C ++, это два языка в одном: C и C с объектами => C ++
sergiol

STL, его можно рассматривать как надстройку, но в нем есть очень практичные классы и контейнеры, которые с помощью шаблонов ЕСТЕСТВЕННО расширяют возможности C ++. И перечисления, о которых я говорил, являются РОДНЫМИ перечислениями. Когда вы говорите о Boost, вы полагаетесь на сторонний код, который не является РОДНЫМ для языка. В C # мне не нужно включать что-либо внешнее, чтобы иметь итеративное перечисление с самообъявлением. В C ++ я часто использую трюк, чтобы вызвать последний элемент перечисления - без присвоенных значений, поэтому они автоматически запускаются с нуля и увеличиваются - что-то вроде NUM_ITEMS, и это дает мне верхнюю границу.
sergiol

2
спасибо за ваше дальнейшее объяснение - я по крайней мере могу видеть, откуда вы; Я слышал, как некоторые люди жалуются, что обучение использованию шаблонов не похоже на другое программирование на C ++, хотя это, конечно, не мой опыт. Я предоставляю другим читателям делать то, что они хотят, из других представленных вами точек зрения. Ура.
Тони Делрой

1
Я думаю, что самая большая проблема с универсальным базовым типом состоит в том, что такой тип был бы бесполезен, если бы у него не было никаких виртуальных методов; C ++ не хотел добавлять какие-либо накладные расходы для типов структур, у которых нет виртуальных методов, но каждый объект любого типа, который имеет какие-либо виртуальные методы, должен иметь как минимум указатель на таблицу диспетчеризации. Если бы классы и структуры населяли разные вселенные, можно было бы иметь универсальный объект класса, но классы и структуры в C ++ в основном одно и то же.
supercat 06
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.