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