c++ 是面向对象的语言
不完全正确,c++ 不仅支持面向对象还支持其他编程方式,不能刻意地限制于面向对象狭隘的层面。它支持一套组合编程技术包括面向对象和泛型编程。通常情况下,一个问题最佳的解决方案涉及多种风格。我说的最佳,意思是代码更简洁,容易理解,更加高效,更容易维护等等
这种观点使人们认为 c++ 相对 c 来说,不是那么必须,除非你需要一个庞大的类层次,并且带有许多虚函数(运行时多态)。对于许多人和许多问题来说,这样使用并不合适。这个流言导致人们的谴责 c++ ,因为它的面向对象不够彻底。毕竟,如果你认为好就是面向对象,显然 c++ 包含更多的非面向对象的东西,因此被认为是不好的,这也为不要学习c++ 提供了一个好的借口。
考虑这样一个例子
void rotate_and_draw(vector<Shape*>& vs, int r)
{
<span style="white-space:pre"> </span>for_each(vs.begin(),vs.end(), [](Shape* p) { p->rotate(r); }); // rotate all elements of vs
<span style="white-space:pre"> </span>for (Shape* p : vs) p->draw(); // draw all elements of vs
}
Is this object-oriented? Of course it is; it relies critically on a class hierarchy with virtual functions. It is generic? Of course it is; it relies critically on a parameterized container (vector) and the generic function for_each. Is this functional? Sort of; it uses a lambda (the [] construct). So what is it? It is modern C++: C++11.
这是面向对象吗?当然是,它很大程度上依赖带有虚函数的类层次结构。它是泛型吗?当然是拉,它同样依赖于参数化的模板容器 vector 和泛型函数 for_each.它是函数式的吗?它使用了 lambda 表达式,这点来说也算是。那么它到底是什么类型的?它就是现代的c++,c++11.
我同时使用了 范围 for 循环和标准库的算法 for_each ,仅仅是为了展示一下这个特性,实际中,我只会用一种循环,用另一种写法
泛型编程
你想让这段代码再通用一点吗(模版化,泛型)?毕竟,它只是用于形状的容器指针。列表和内置数组会怎样呢?像 shared_ptr 和 unique_ptr 的智能指针呢?那些不叫 Shape 的类可以用 draw() 和 rotate() 吗?想一想:
template<typename Iter>
void rotate_and_draw(Iter first, Iter last, int r)
{
<span style="white-space:pre"> </span>for_each(first,last,[](auto p) { p->rotate(r); }); // rotate all elements of [first:last)
<span style="white-space:pre"> </span>for (auto p = first; p!=last; ++p) p->draw(); // draw all elements of [first:last)
}
This works for any sequence you can iterate through from first to last. That’s the style of the C++ standard-library algorithms. I used auto to avoid having to name the type of the interface to “shape-like objects.” That’s a C++11 feature meaning “use the type of the expression used as initializer,” so for the for-loop p’s type is deduced to be whatever type first is. The use of auto to denote the argument type of a lambda is a C++14 feature, but already in use.
这段代码适用于任何可以从头到尾迭代的序列。这就是c++ 标准库算法的风格。我使用了 auto 关键字避免为类似 Shape 对象的接口类型命名。这是c++11的一个特性,意思是使用表达式的类型作为初始化类型,对于 for 循环来说,指针 p 的类型是由 Iter first 的类型得出的。使用 auto 表示 lambda 表达式参数的类型,是c++14的特征,但是现在已经可以用了。
思考一下:
void user(list<unique_ptr<Shape>>& lst, Container<Blob>& vb)
{
<span style="white-space:pre"> </span>rotate_and_draw(lst.begin(),lst.end());
<span style="white-space:pre"> </span>rotate_and_draw(begin(vb),end(vb));
}
Here, I assume that Blob is some graphical type with operations draw() and rotate() and that Container is some container type. The standard-library list (std::list) has member functions begin() and end() to help the user traverse its sequence of elements. That’s nice and classical OOP. But what if Container is something that does not support the C++ standard library’s notion of iterating over a half-open sequence, [b:e)? Something that does not have begin() and end() members? Well, I have never seen something container-like, that I couldn’t traverse, so we can define free-standing begin() and end() with appropriate semantics. The standard library provides that for C-style arrays, so if Container is a C-style array, the problem is solved – and C-style arrays are still very common.
在这段代码里,我假设 Bolb 是一个图像类型,带有draw() and rotate(),Container 是任意的容器类型。标准库的 list 有2个成员函数 begin() end() ,可以用于函数 user 遍历它序列中元素。这是典型的 面向对象编程。但是,如果类型 Container 不支持 c++ 标准里半开区间的迭代概念呢?或者没有 begin() end()的成员呢?当然,我从没见过容器类型不能遍历,那么我们可以自定义合适的 begin() end().标准库为 c 风格的数组提供了上面的成员,所以即便 Container 是c 风格的数组,问题也可以解决,c 风格的数组仍然常用。
适用性
思考一个复杂的情况,如果 Container 存储对象的指针,有一套不同访问和遍历方式。举例,假设你可以这样访问 Container 的元素
for (auto p = c.first(); p!=nullptr; p=c.next()) { /* do something with *p */}
This style is not uncommon. We can map it to a [b:e) sequence like this
这种样式不常见,我们将区间指针做映射像下面这样
template<typename T> struct Iter {
<span style="white-space:pre"> </span>T* current;
<span style="white-space:pre"> </span>Container<T>& c;
};
template<typename T> Iter<T> begin(Container<T>& c) { return Iter<T>{c.first(),c}; }
template<typename T> Iter<T> end(Container<T>& c) { return Iter<T>{nullptr}; }
template<typename T> Iter<T> operator++(Iter<T> p) { p.current = c.next(); return this; }
template<typename T> T* operator*(Iter<T> p) { return p.current; }
Note that this is modification is nonintrusive: I did not have to make changes to Container or some Container class hierarchy to map Container into the model of traversal supported by the C++ standard library. It is a form of adaptation, rather than a form of refactoring.
注意这个修改是无关紧要的,我并没有为了把容器映射成c++ 标准库支持的迭代的模型而改写容器或容器类的层次机构。这只是一种改写的形式并不算重构。
我选择这个例子是为了说明泛型编程技术并不只在标准库中广泛使用。对于一些很普通的面向对象的定义,其实他们并不是面向对象的
c++ 必须是面向对象(层次结构和虚函数的滥用)的想法会严重危害到性能评价。如果你需要运行时解决一组类型时,OOP是非常棒的。我经常这样用。但是它相对也比较死板(不是所有相关的类型都刚好嵌入同一层次结构)而且虚函数会抑制内联(在处理简单重要的工作时,这回大大增加耗时)