Я имею в виду, кроме его обязательного имени (Стандартная библиотека шаблонов) ...
Изначально C ++ предназначался для представления концепций ООП в C. То есть: вы можете сказать, что конкретная сущность может и не может делать (независимо от того, как она это делает), основываясь на своем классе и иерархии классов. Некоторые композиции способностей сложнее описать таким образом из-за проблематики множественного наследования и того факта, что C ++ поддерживает концепцию интерфейсов несколько неуклюже (по сравнению с Java и т. Д.), Но она есть (и может быть улучшенный).
И тогда в игру вступили шаблоны вместе с STL. STL, казалось, взял классические концепции ООП и полностью их уничтожил, используя вместо этого шаблоны.
Должно быть различие между случаями, когда шаблоны используются для обобщения типов, когда сами типы типов не имеют отношения к работе шаблона (контейнеры, например). Наличие vector<int>
имеет смысл.
Однако во многих других случаях (итераторы и алгоритмы) предполагается, что шаблонные типы должны следовать «концепции» (Input Iterator, Forward Iterator и т. Д.), Где фактические детали концепции полностью определяются реализацией шаблона. функция / класс, а не классом типа, используемого с шаблоном, что несколько противоречит использованию ООП.
Например, вы можете сказать функцию:
void MyFunc(ForwardIterator<...> *I);
Обновление: как было непонятно в исходном вопросе, ForwardIterator вполне может быть сам шаблонизированным, чтобы разрешить любой тип ForwardIterator. Напротив, в качестве концепции используется ForwardIterator.
ожидает прямого итератора, только взглянув на его определение, где вам нужно будет посмотреть на реализацию или документацию для:
template <typename Type> void MyFunc(Type *I);
Я могу сделать два утверждения в пользу использования шаблонов: скомпилированный код можно сделать более эффективным путем индивидуальной компиляции шаблона для каждого используемого типа вместо использования vtables. И тот факт, что шаблоны могут использоваться с нативными типами.
Однако я ищу более глубокую причину, почему отказываться от классического ООП в пользу шаблонов для STL? (Предполагая, что вы читали это далеко: P)
vector<int>
иvector<char>
использоваться одновременно. Они могут, конечно, но вы можете использовать любые две части кода одновременно. Это не имеет ничего общего с шаблонами, C ++ или STL. В создании экземпляра нет ничего,vector<int>
что требовалоvector<char>
бы загрузки или выполнения кода.Ответы:
Короткий ответ: «потому что C ++ перешел». Да, еще в конце 70-х годов Страуструп планировал создать модернизированный C с возможностями ООП, но это давно. К тому времени, когда язык был стандартизирован в 1998 году, он больше не был языком ООП. Это был мультипарадигмальный язык. Он, конечно, имел некоторую поддержку кода ООП, но он также имел наложенный на тьюринг язык шаблонов, он позволял метапрограммировать во время компиляции, и люди открыли универсальное программирование. Внезапно ООП показалось не таким уж важным. Не тогда, когда мы можем писать более простой, краткий и эффективный код, используя методы, доступные через шаблоны и общее программирование.
ООП не святой Грааль. Это милая идея, и она значительно улучшилась по сравнению с процедурными языками еще в 70-х годах, когда она была изобретена. Но, честно говоря, это еще не все, что нужно. Во многих случаях это неуклюжий и многословный, и это действительно не продвигает повторно используемый код или модульность.
Вот почему сообщество C ++ сегодня гораздо больше интересуется универсальным программированием, и почему все наконец начинают понимать, что функциональное программирование также довольно умно. ООП само по себе просто не симпатичное зрелище.
Попробуйте нарисовать граф зависимостей гипотетического "ООП-ified" STL. Сколько классов нужно знать друг о друге? Было бы много зависимостей. Сможете ли вы включить только
vector
заголовок, не вставляяiterator
и даже неiostream
вставляя? STL делает это легко. Вектор знает о типе итератора, который он определяет, и это все. Алгоритмы STL ничего не знают . Им даже не нужно включать заголовок итератора, хотя они все принимают итераторы в качестве параметров. Что является более модульным?STL может не следовать правилам ООП, как это определяет Java, но разве он не достигает целей ООП? Разве это не обеспечивает многократное использование, низкую связь, модульность и инкапсуляцию?
И разве он не достигает этих целей лучше, чем версия с ООП?
Что касается того, почему STL был принят в язык, произошло несколько вещей, которые привели к STL.
Сначала шаблоны были добавлены в C ++. Они были добавлены по той же причине, что дженерики были добавлены в .NET. Казалось хорошей идеей иметь возможность писать такие вещи, как «контейнеры типа T», не отказываясь от безопасности типов. Конечно, реализация, на которой они остановились, была намного более сложной и мощной.
Затем люди обнаружили, что механизм шаблонов, который они добавили, оказался даже более мощным, чем ожидалось. И кто-то начал экспериментировать с использованием шаблонов для написания более общей библиотеки. Один вдохновлен функциональным программированием и использует все новые возможности C ++.
Он представил его комитету по языку C ++, которому понадобилось немало времени, чтобы привыкнуть к нему, потому что он выглядел так странно и по-разному, но в конечном итоге понял, что он работает лучше, чем традиционные эквиваленты ООП, которые они должны были бы включить в противном случае . Таким образом, они внесли несколько изменений в него и приняли его в стандартную библиотеку.
Это был не идеологический выбор, это был не политический выбор «хотим ли мы быть ООП или нет», а очень прагматичный. Они оценили библиотеку и увидели, что она работает очень хорошо.
В любом случае, обе причины, упомянутые вами в пользу STL, абсолютно необходимы.
Стандартная библиотека C ++ имеет , чтобы быть эффективным. Если он менее эффективен, чем, скажем, эквивалентный свернутый вручную код C, то люди не будут его использовать. Это снизит производительность, увеличит вероятность ошибок и в целом будет плохой идеей.
И STL должен работать с примитивными типами, потому что примитивные типы - это все, что у вас есть в C, и они являются основной частью обоих языков. Если бы STL не работал с собственными массивами, это было бы бесполезно .
Ваш вопрос имеет сильное предположение, что ООП является "лучшим". Мне любопытно услышать почему. Вы спрашиваете, почему они "отказались от классической ООП". Мне интересно, почему они должны были придерживаться этого. Какие преимущества это имело бы?
источник
std::set
бы пример. Он не наследуется от абстрактного базового класса. Как это ограничивает ваше использованиеstd::set
? Есть ли что-то, что вы не можете сделать с a,std::set
потому что оно не наследуется от абстрактного базового класса?Самый прямой ответ на то, о чем я думаю, вы спрашиваете / жалуетесь на это: предположение, что C ++ является языком ООП, является ложным предположением.
C ++ - это мультипарадигмальный язык. Он может быть запрограммирован с использованием принципов ООП, он может быть запрограммирован процедурно, он может быть запрограммирован в общем (шаблоны), а с C ++ 11 (ранее известный как C ++ 0x) некоторые вещи могут быть даже запрограммированы функционально.
Разработчики C ++ видят в этом преимущество, поэтому они утверждают, что ограничение C ++ действовать как чисто ООП-язык, когда универсальное программирование решает проблему лучше и, ну, в общем , было бы шагом назад.
источник
Насколько я понимаю, Страуструп изначально предпочитал конструкцию контейнера в стиле «ООП» и фактически не видел другого способа сделать это. Александр Степанов является ответственным за STL, и в его задачи не входило «сделать его объектно-ориентированным» :
(Он объясняет, почему наследование и виртуалы - так называемый объектно-ориентированный дизайн «были в корне ошибочными и не должны использоваться» в оставшейся части интервью).
Как только Степанов представил свою библиотеку Страуструпу, Страуструп и другие предприняли невероятные усилия, чтобы привести ее в стандарт ISO C ++ (то же интервью):
источник
Ответ можно найти в этом интервью со Степановым, автором STL:
источник
Почему чистый ООП-проект для библиотеки структуры данных и алгоритмов был бы лучше ?! ООП не решение для каждой вещи.
ИМХО, STL - самая элегантная библиотека, которую я когда-либо видел :)
на твой вопрос,
вам не нужен полиморфизм во время выполнения, для STL является преимуществом реализовать библиотеку с использованием статического полиморфизма, что означает эффективность. Попробуйте написать общую сортировку или расстояние или какой-либо другой алгоритм, который применяется ко ВСЕМ контейнерам! Ваша сортировка в Java будет вызывать функции, которые динамически выполняются через n уровней!
Вам нужны такие глупые вещи, как Boxing и Unboxing, чтобы скрыть неприятные предположения о так называемых языках Pure OOP.
Единственная проблема, которую я вижу с STL и шаблонами в целом, это ужасные сообщения об ошибках. Который будет решен с использованием концепций в C ++ 0X.
Сравнение STL с коллекциями на Java - это все равно, что сравнивать Тадж-Махал с моим домом :)
источник
static_assert
возможно.Я думаю, что вы неправильно понимаете предполагаемое использование концепций шаблонами. Например, Forward Iterator - это очень четкое понятие. Чтобы найти выражения, которые должны быть действительными, чтобы класс являлся прямым итератором, и их семантику, включая сложность вычислений, вы посмотрите на стандарт или на http://www.sgi.com/tech/stl/ForwardIterator.html. (Вы должны перейти по ссылкам на Input, Output и Trivial Iterator, чтобы увидеть все это).
Этот документ является очень хорошим интерфейсом, и «реальные детали концепции» определены здесь же. Они не определяются реализациями прямых итераторов и не определяются алгоритмами, использующими прямые итераторы.
Различия в том, как обрабатываются интерфейсы между STL и Java, имеют три аспекта:
1) STL определяет допустимые выражения, используя объект, тогда как Java определяет методы, которые должны вызываться на объекте. Конечно, допустимым выражением может быть вызов метода (функции-члена), но это не обязательно.
2) Java-интерфейсы являются объектами времени выполнения, тогда как концепции STL не видны во время выполнения даже с RTTI.
3) Если вы не в состоянии сделать правильными требуемые допустимые выражения для концепции STL, вы получите неопределенную ошибку компиляции, когда создаете экземпляр шаблона с типом. Если вам не удается реализовать обязательный метод интерфейса Java, вы получаете конкретную ошибку компиляции, говорящую об этом.
Эта третья часть, если вам нравится своего рода (тип компиляции) "утиная типизация": интерфейсы могут быть неявными. В Java интерфейсы являются несколько явными: класс «is» Iterable, если и только если он говорит, что он реализует Iterable. Компилятор может проверить, что все сигнатуры его методов присутствуют и являются правильными, но семантика все еще неявна (т.е. они либо документированы, либо нет, но только больше кода (модульных тестов) может сказать вам, является ли реализация правильной).
В C ++, как и в Python, семантика и синтаксис неявны, хотя в C ++ (и в Python, если вы получаете препроцессор строгой типизации) вы получаете некоторую помощь от компилятора. Если программисту требуется явное объявление интерфейсов в Java-стиле реализующим классом, тогда стандартным подходом является использование признаков типа (а множественное наследование может предотвратить это слишком многословно). По сравнению с Java не хватает одного шаблона, который я могу создать для своего типа и который будет скомпилирован тогда и только тогда, когда все необходимые выражения допустимы для моего типа. Это скажет мне, реализовал ли я все необходимые биты, «прежде чем я его использую». Это удобно, но это не ядро ООП (и оно все еще не проверяет семантику,
STL может или не может быть достаточно ОО на ваш вкус, но он, безусловно, четко отделяет интерфейс от реализации. Ему не хватает способности Java отражать интерфейсы, и он по-разному сообщает о нарушениях интерфейса.
Лично я считаю, что неявные типы являются сильной стороной при правильном использовании. Алгоритм говорит, что он делает со своими параметрами шаблона, и разработчик следит за тем, чтобы эти вещи работали: это в точности общий знаменатель того, что должны делать «интерфейсы». Кроме того, с STL вы вряд ли будете использовать, скажем,
std::copy
основанный на поиске его форвард-декларации в заголовочном файле. Программисты должны выяснить, что берет функция, основываясь на ее документации, а не только на сигнатуре функции. Это верно в C ++, Python или Java. Существуют ограничения на то, что может быть достигнуто с помощью набора текста на любом языке, и попытка использовать набор для выполнения того, чего он не делает (проверьте семантику), будет ошибкой.Тем не менее, алгоритмы STL обычно называют свои параметры шаблона таким образом, чтобы было понятно, какая концепция требуется. Однако это делается для того, чтобы предоставить полезную дополнительную информацию в первой строке документации, а не для того, чтобы сделать предварительные декларации более информативными. Есть больше вещей, которые вы должны знать, чем могут быть включены в типы параметров, поэтому вы должны прочитать документы. (Например, в алгоритмах, которые принимают входной диапазон и выходной итератор, есть вероятность, что выходному итератору нужно достаточно «пространства» для определенного количества выходных данных, исходя из размера входного диапазона и, возможно, его значений. Попробуйте ввести его строго. )
Вот Бьярне о явно объявленных интерфейсах: http://www.artima.com/cppsource/cpp0xP.html
Если взглянуть на это с другой стороны, с помощью утки, вы можете реализовать интерфейс, не зная, что интерфейс существует. Или кто-то может написать интерфейс намеренно, чтобы ваш класс реализовал его, проконсультировавшись с вашими документами, чтобы убедиться, что они не просят ничего, чего вы еще не делали. Это гибко.
источник
std
библиотеку, которая не соответствует концепции, обычно "плохо сформирована, диагностика не требуется".«Для меня ООП означает только обмен сообщениями, локальное хранение, защиту и сокрытие процесса состояния и крайнюю позднюю привязку всех вещей. Это может быть сделано в Smalltalk и в LISP. Возможно, есть другие системы, в которых это возможно, но Я не знаю о них. " - Алан Кей, создатель Smalltalk.
C ++, Java и большинство других языков довольно далеки от классического ООП. Тем не менее, отстаивание идеологий не очень продуктивно. C ++ не является чистым в любом смысле, поэтому он реализует функциональность, которая кажется прагматичной в то время.
источник
STL начал с намерения предоставить большую библиотеку, охватывающую наиболее часто используемый алгоритм - с целью согласованного поведения и производительности . Шаблон стал ключевым фактором, чтобы сделать эту реализацию и цель осуществимой.
Просто чтобы дать еще одну ссылку:
Интервью Аль Стивенса Алекс Степанов, в марте 1995 года DDJ:
Степанов объяснил свой опыт работы и выбор большой библиотеки алгоритмов, которая в конечном итоге превратилась в STL.
источник
Основная проблема с
как вы можете безопасно получить тип того, что возвращает итератор? С помощью шаблонов это делается для вас во время компиляции.
источник
Давайте подумаем о стандартной библиотеке как о базе данных коллекций и алгоритмов.
Если вы изучали историю баз данных, вы, несомненно, знаете, что в самом начале базы данных были в основном «иерархическими». Иерархические базы данных очень близко соответствовали классическим ООП - в частности, разновидности с единым наследованием, например, используемой Smalltalk.
Со временем стало очевидно, что иерархические базы данных можно использовать для моделирования практически чего угодно, но в некоторых случаях модель с одним наследованием была довольно ограничивающей. Если у вас была деревянная дверь, было бы удобно смотреть на нее как на дверь или на кусок какого-то сырья (сталь, дерево и т. Д.).
Итак, они изобрели базы данных сетевой модели. Базы данных сетевой модели очень близко соответствуют множественному наследованию. C ++ полностью поддерживает множественное наследование, в то время как Java поддерживает ограниченную форму (вы можете наследовать только от одного класса, но также можете реализовать столько интерфейсов, сколько захотите).
Как базы данных иерархической модели, так и базы данных сетевой модели в основном исчезли из общего назначения (хотя некоторые остаются в довольно специфических нишах). Для большинства целей они были заменены реляционными базами данных.
Большая часть реляционных баз данных стала универсальной. Реляционная модель функционально является надмножеством сетевой модели (которая, в свою очередь, является надмножеством иерархической модели).
C ++ во многом шел по тому же пути. Соответствие между единичным наследованием и иерархической моделью, а также между множественным наследованием и сетевой моделью довольно очевидно. Соответствие между шаблонами C ++ и иерархической моделью может быть менее очевидным, но в любом случае это довольно близкое соответствие.
Я не видел формального доказательства этого, но я считаю, что возможности шаблонов - это расширенный набор возможностей, предоставляемых множественным наследованием (что явно является расширенным набором одиночного наследования). Одна хитрость заключается в том, что шаблоны в основном статически связаны, то есть все связывание происходит во время компиляции, а не во время выполнения. Таким образом, формальное доказательство того, что наследование обеспечивает расширенный набор возможностей наследования, может быть довольно сложным и сложным (или даже невозможным).
В любом случае, я думаю, что это основная причина того, что C ++ не использует наследование для своих контейнеров - нет никакой реальной причины для этого, потому что наследование предоставляет только подмножество возможностей, предоставляемых шаблонами. Поскольку шаблоны в большинстве случаев являются необходимостью, их также можно использовать практически везде.
источник
Как вы делаете сравнения с ForwardIterator *? То есть, как вы проверяете, что у вас есть то, что вы ищете, или вы его прошли?
Большую часть времени я бы использовал что-то вроде этого:
Это означает, что я знаю, что я указываю на MyType, и я знаю, как их сравнить. Хотя это выглядит как шаблон, на самом деле это не так (без ключевого слова template).
источник
На этот вопрос есть много хороших ответов. Также следует отметить, что шаблоны поддерживают открытый дизайн. При текущем состоянии объектно-ориентированных языков программирования при работе с такими проблемами необходимо использовать шаблон посетителя, и истинный ООП должен поддерживать множественное динамическое связывание. См. Open Multi-Methods for C ++, P. Pirkelbauer, et.al. для очень интересного чтения.
Еще одним интересным моментом шаблонов является то, что они могут быть использованы для полиморфизма во время выполнения. Например
Обратите внимание, что эта функция также будет работать, если
Value
это какой-то вектор ( не std :: vector, который следует вызыватьstd::dynamic_array
во избежание путаницы)Если
func
оно мало, эта функция много выиграет от встраивания. Пример использованияВ этом случае вы должны знать точный ответ (2.718 ...), но легко построить простой ODE без элементарного решения (подсказка: используйте многочлен от y).
Теперь у вас есть большое выражение
func
, и вы используете ODE-решатель во многих местах, поэтому ваш исполняемый файл повсеместно загрязнен экземплярами шаблонов. Что делать? Первое, на что нужно обратить внимание, это то, что обычный указатель функции работает. Затем вы хотите добавить карри, чтобы вы написали интерфейс и явную реализациюНо приведенная выше реализация работает только для того
double
, почему бы не написать интерфейс в качестве шаблона:и специализируются на некоторых распространенных типах значений:
Если бы функция сначала была разработана на основе интерфейса, то вы были бы вынуждены наследовать от этого ABC. Теперь у вас есть эта опция, а также указатель функции, лямбда или любой другой объект функции. Ключевым моментом здесь является то, что мы должны иметь
operator()()
, и мы должны иметь возможность использовать некоторые арифметические операторы для его возвращаемого типа. Таким образом, механизм шаблона в этом случае сломался бы, если бы в C ++ не было перегрузки операторов.источник
Концепция отделения интерфейса от интерфейса и возможности замены реализаций не является неотъемлемой частью объектно-ориентированного программирования. Я полагаю, что эта идея была разработана в компонентной разработке, такой как Microsoft COM. (См. Мой ответ на тему «Что такое компонентно-управляемая разработка?»). Когда дети росли и изучали C ++, люди были лишены наследственности и полиморфизма. Так продолжалось до 90-х годов, когда люди начали говорить «Программируйте на« интерфейс », а не на« реализацию »и« Композицию объекта Favor », а не« наследование классов »». (оба из которых цитируются GoF, кстати).
Затем появилась Java со встроенным сборщиком мусора и
interface
ключевым словом, и внезапно стало практически возможным разделить интерфейс и реализацию. Прежде чем вы это знаете, идея стала частью ОО. C ++, шаблоны и STL предшествуют всему этому.источник