C++拾趣——使用多态减少泛型带来的代码膨胀
? ? ? ? 泛型編程是C++語言中一種非常重要的技術,它可以讓我們大大減少相似代碼編寫量。有時候,我和同事提及該技術時,稱它是“一種讓編譯器幫我們寫代碼的技術”。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? C++是一門靜態語言,它最終的編譯成果是可以直接運行于馮諾依曼體系的計算機上,而不像其他動態語言,可以運行于虛擬機等容器中。由于對運行效率得追求,C++也是一門類型精確的語言,即object是什么類型,在編譯時往往就要確定好,這種方式可以稱為數據的靜態綁定。
template<class T>
void call_function(T& f) {f();
};class PrintA {
public:virtual void operator ()() {std::cout << "Print A" << std::endl;}
};class PrintB : public PrintA {
public:virtual void operator ()() {std::cout << "Print B" << std::endl;}
};int main() {PrintA a;PrintB b;call_function(a);call_function(b);return 0;
}
? ? ? ? 上例中,我們只寫了一個call_function模板函數,但是最終編譯器會將其翻譯成為兩個獨立的函數,分別是call_function<PrintA>()和call_function<PrintB>()。這也是之前所述“一種讓編譯器幫我們寫代碼的技術”的表現。
? ? ? ? 我們逆向上述代碼來驗證下
? ? ? ? 上圖我們看到call_function<PrintA>()和call_function<PrintB>()方法的地址是不同的。這就意味著,這兩個方法擁有各自的代碼邏輯。再上升一個層次去看,使用call_function的模板方法的類有多少種,就會產生多少個相應的特化方法。我們只寫了一個模板方法,但是編譯器最終幫我們生成了多個,這個過程和現象我們稱為發生了“代碼膨脹”。
? ? ? ? 編譯器將類型特化,即精確指定了類型,這就使得C++程序在運行時直接跳轉到相應函數地址就行,而不需要做類型判別后去路由。這也是C++高效的一個重要原因。
? ? ? ? 除了靜態綁定,C++還有半動態綁定。這也是C++實現多態的技術基礎。我們可以使用該技術,部分的解決泛型技術帶來的“代碼膨脹”的問題。
? ? ? ? 以上例為例,我們只要增加如下方法就行
void call(PrintA& f) {f();
}
? ? ? ? 然后調用call(a)和call(b)
? ? ? ? 可以看到,兩次調用的call方法指向了同一個地址。于是不管call方法操作的類型有多少個,它都沒有導致代碼的膨脹。
? ? ? ? 需要指出的是,泛型和多態在上例中,體現了“空間”和“時間”的選擇問題。當我們在做優化代碼時,往往最終會走到“時間換空間”或者“空間換時間”的選擇中。
? ? ? ? 上例泛型技術,生成了多份函數。在調用時,方法對應的函數地址是確定的,于是這是種調用是高效的。這是“空間換時間”的案例。
? ? ? ? 上例多態技術,只生成了一份代碼。在調用時,call方法需要找到object的虛表,然后計算出虛函數的地址,最后才能調用相應的虛函數。這個過程沒有直接call一個地址快。這是“時間換空間”的案例。
? ? ? ? 目前CPU的發展已經進入瓶頸,磁盤的空間卻越來越便宜。很多人可能覺得“空間換時間”是個更好的選擇,其實不可以一概而論。因為如果程序的最終編譯產物小,其在CPU指令緩存中發生了cache?miss也可能變小,最終效率可能還是可觀的。
總結
以上是生活随笔為你收集整理的C++拾趣——使用多态减少泛型带来的代码膨胀的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从0开始搭建编程框架——插件
- 下一篇: bug诞生记——临时变量、栈变量导致的双