Почему C ++ STL так сильно основан на шаблонах? (а не на * интерфейсах *) - PullRequest
207 голосов
/ 24 июня 2009

Я имею в виду, кроме его обязательного имени (Стандартная библиотека шаблонов) ...

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)

Ответы [ 13 ]

0 голосов
/ 16 февраля 2017

На этот вопрос много хороших ответов. Также следует отметить, что шаблоны поддерживают открытый дизайн. В текущем состоянии объектно-ориентированных языков программирования при работе с такими проблемами необходимо использовать шаблон посетителя, и истинный ООП должен поддерживать множественное динамическое связывание. См. Open Multi-Methods для C ++, P. Pirkelbauer и др. для очень интересного чтения.

Еще одним интересным моментом шаблонов является то, что они могут использоваться для полиморфизма во время выполнения. Например

template<class Value,class T>
Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func)
    {
    auto dt=(t_end-t_0)/N;
    for(size_t k=0;k<N;++k)
        {y_0+=func(t_0 + k*dt,y_0)*dt;}
    return y_0;
    }

Обратите внимание, что эта функция также будет работать, если Value - это какой-то вектор (, а не std :: vector, который следует называть std::dynamic_array, чтобы избежать путаницы)

Если func мало, эта функция сильно выиграет от встраивания. Пример использования

auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y)
    {return y;});

В этом случае вы должны знать точный ответ (2.718 ...), но легко построить простой ODE без элементарного решения (подсказка: используйте полином по y).

Теперь у вас есть большое выражение в func, и вы используете решатель ODE во многих местах, так что ваш исполняемый файл везде загрязнен экземплярами шаблонов. Что делать? Первое, на что нужно обратить внимание, это то, что обычный указатель функции работает. Затем вы хотите добавить карри, чтобы вы написали интерфейс и явный экземпляр

class OdeFunction
    {
    public:
        virtual double operator()(double t,double y) const=0;
    };

template
double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func);

Но приведенная выше реализация работает только для double, почему бы не написать интерфейс как шаблон:

template<class Value=double>
class OdeFunction
    {
    public:
        virtual Value operator()(double t,const Value& y) const=0;
    };

и специализируются на некоторых распространенных типах значений:

template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction<double>& func);

template vec4_t<double> euler_fwd(size_t N,double t_0,double t_end,vec4_t<double> y_0,const OdeFunction< vec4_t<double> >& func); // (Native AVX vector with four components)

template vec8_t<float> euler_fwd(size_t N,double t_0,double t_end,vec8_t<float> y_0,const OdeFunction< vec8_t<float> >& func); // (Native AVX vector with 8 components)

template Vector<double> euler_fwd(size_t N,double t_0,double t_end,Vector<double> y_0,const OdeFunction< Vector<double> >& func); // (A N-dimensional real vector, *not* `std::vector`, see above)

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

0 голосов
/ 25 июня 2009

Концепция отделения интерфейса от интерфейса и возможности замены реализаций не свойственна объектно-ориентированному программированию. Я полагаю, что эта идея была разработана в компонентной разработке, такой как Microsoft COM. (См. мой ответ о том, что такое компонентно-ориентированная разработка?). Когда дети росли и изучали C ++, люди были лишены наследования и полиморфизма. Так продолжалось до 90-х годов, когда люди начали говорить «Программируйте на« интерфейс », а не на« реализацию »и« Композицию объекта Favor », а не« наследование классов »». (оба из которых цитируются GoF кстати).

Затем появилась Java со встроенным сборщиком мусора и ключевым словом interface, и внезапно стало практичным разделить интерфейс и реализацию. Прежде чем вы это знаете, идея стала частью ОО. C ++, шаблоны и STL предшествуют всему этому.

0 голосов
/ 24 июня 2009

Как вы сравниваете с ForwardIterator *? То есть, как вы проверяете, что у вас есть то, что вы ищете, или вы его прошли?

Большую часть времени я бы использовал что-то вроде этого:

void MyFunc(ForwardIterator<MyType>& i)

, что означает, что я знаю, что указываю на MyType's, и я знаю, как их сравнивать. Хотя это выглядит как шаблон, на самом деле это не так (без ключевого слова template).

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