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是非常棒的。我經常這樣用。但是它相對也比較死板(不是所有相關的類型都剛好嵌入同一層次結構)而且虛函數會抑制內聯(在處理簡單重要的工作時,這回大大增加耗時)