我的意思是,除了它的名字标准模板库(后来演变成c++标准库)。

c++最初将面向对象的概念引入到C中,也就是说:您可以根据特定实体的类和类层次结构来判断它能做什么和不能做什么(不管它是如何做的)。由于多重继承的复杂性,一些能力的组合更难以用这种方式描述,而且c++以一种有点笨拙的方式支持仅接口继承(与java等相比),但它确实存在(并且可以改进)。

然后模板开始发挥作用,还有STL。STL似乎采用了经典的OOP概念,并将它们冲入下水道,取而代之的是使用模板。

当模板用于泛化类型时,类型本身与模板的操作无关(例如容器),这两种情况之间应该有区别。vector<int>非常有意义。

然而,在许多其他情况下(迭代器和算法),模板化类型应该遵循一个“概念”(Input Iterator, Forward Iterator,等等),其中概念的实际细节完全由模板函数/类的实现定义,而不是由模板使用的类型的类定义,这在某种程度上是对OOP的反使用。

例如,你可以告诉函数:

void MyFunc(ForwardIterator<...> *I);

更新:由于在最初的问题中不清楚,ForwardIterator本身可以被模板化以允许任何ForwardIterator类型。相反,将ForwardIterator作为一个概念。

只有通过查看它的定义才能期望Forward Iterator,在这里你需要查看以下方面的实现或文档:

template <typename Type> void MyFunc(Type *I);

我可以提出两个主张来支持使用模板:通过为每个使用的类型重新编译模板,而不是使用动态分派(主要通过虚表),可以使编译后的代码更加高效。2. 事实上,模板可以与原生类型一起使用。

然而,我正在寻找一个更深刻的原因,放弃经典的面向对象的支持模板的STL?


当前回答

这个问题有很多很好的答案。还应该提到模板支持开放设计。在面向对象编程语言的当前状态下,在处理此类问题时必须使用访问者模式,而真正的OOP应该支持多个动态绑定。参见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求解器,因此您的可执行文件到处都受到模板实例化的污染。怎么办呢?首先要注意的是,常规函数指针可以工作。然后,您希望添加curry,以便编写接口和显式实例化

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。现在您有了这个选项,还有函数指针、lambda或任何其他函数对象。这里的关键是我们必须有operator()(),我们必须能够在它的返回类型上使用一些算术运算符。因此,在这种情况下,如果c++没有操作符重载,模板机制就会中断。

其他回答

STL最初的目的是提供一个涵盖最常用算法的大型库,其目标是一致的行为和性能。模板是使实现和目标可行的关键因素。

只是为了提供另一个参考:

1995年3月,《DDJ》的艾尔·史蒂文斯采访亚历克斯·斯捷潘诺夫:

http://www.sgi.com/tech/stl/drdobbs-interview.html

Stepanov解释了他的工作经验和对大型算法库的选择,最终演变为STL。

Tell us something about your long-term interest in generic programming .....Then I was offered a job at Bell Laboratories working in the C++ group on C++ libraries. They asked me whether I could do it in C++. Of course, I didn't know C++ and, of course, I said I could. But I couldn't do it in C++, because in 1987 C++ didn't have templates, which are essential for enabling this style of programming. Inheritance was the only mechanism to obtain genericity and it was not sufficient. Even now C++ inheritance is not of much use for generic programming. Let's discuss why. Many people have attempted to use inheritance to implement data structures and container classes. As we know now, there were few if any successful attempts. C++ inheritance, and the programming style associated with it are dramatically limited. It is impossible to implement a design which includes as trivial a thing as equality using it. If you start with a base class X at the root of your hierarchy and define a virtual equality operator on this class which takes an argument of the type X, then derive class Y from class X. What is the interface of the equality? It has equality which compares Y with X. Using animals as an example (OO people love animals), define mammal and derive giraffe from mammal. Then define a member function mate, where animal mates with animal and returns an animal. Then you derive giraffe from animal and, of course, it has a function mate where giraffe mates with animal and returns an animal. It's definitely not what you want. While mating may not be very important for C++ programmers, equality is. I do not know a single algorithm where equality of some kind is not used.

对于我认为你在问或抱怨的问题,最直接的回答是:假设c++是一种面向对象语言是一个错误的假设。

c++是一种多范式语言。它可以使用面向对象原则编程,可以程序化编程,也可以泛型编程(模板),使用c++ 11(以前称为c++ 0x)甚至可以对某些东西进行函数式编程。

c++的设计者认为这是一种优势,所以他们会争辩说,当泛型编程能更好地解决问题,并且更通用地解决问题时,限制c++像纯粹的面向对象语言一样运行将是一种倒退。

如何与ForwardIterator*进行比较?也就是说,你如何检查你所拥有的物品是否是你正在寻找的,或者你已经错过了它?

大多数时候,我会使用这样的方法:

void MyFunc(ForwardIterator<MyType>& i)

这意味着我知道I指向MyType的,我知道如何比较它们。虽然它看起来像一个模板,但实际上不是(没有“template”关键字)。

为什么数据结构和算法库的纯面向对象设计会更好?! 面向对象编程并不是所有事情的解决方案。

恕我直言,STL是我见过的最优雅的图书馆:)

关于你的问题,

你不需要运行时多态性,STL实际上使用静态多态性实现库是一个优势,这意味着效率。 试着写一个通用的Sort或Distance或任何适用于所有容器的算法! 你在Java中的Sort会调用动态的n级函数来执行!

你需要像装箱和开箱这样的愚蠢的东西来隐藏所谓的纯OOP语言的讨厌的假设。

我在STL和模板中看到的唯一问题是糟糕的错误消息。 这将使用c++ 0X中的概念来解决。

将STL与Java中的集合进行比较,就像将泰姬陵与我的房子进行比较:)

最基本的问题是

void MyFunc(ForwardIterator *I);

你如何安全地获取迭代器返回的东西的类型?对于模板,这是在编译时为您完成的。