1. 程式人生 > >C++中泛型使用導致的膨脹問題

C++中泛型使用導致的膨脹問題

博主從事C++軟體開發多年,由於之前的開發環境都是資源充足的伺服器,不用考慮磁碟空間的問題。最近打算在智慧家居主機的嵌入式平臺上使用C++進行開發。FLASH儲存空間有限,這是必須要考慮的因素,一定要重視。

如下定義兩個list,元素型別不同:

12 list<int>l1;list<string>l2;

如果是用C語來做應該怎麼辦?它會對應list寫一套程式碼,再對list寫一套。每套都有相同的成員函式,只是變數型別各自不同罷了。

下面是list<int>的C語言實現方式:

123456789101112131415 //! code-1structlist_int_item{intvalue;structlist_int_item *next;};structlist_int{structlist_int_item *head;size_t size;};voidlist_int_insert(structlist_int *p,intvalue);intlist_int_sort(structlist_int *p);boollist_int_empty(structlist_int *
p);...

下面是list<string>的C語言實現方式:

123456789101112131415 //! code-2structlist_string_item{stringvalue;structlist_string_item *next;};structlist_string{structlist_string_item *head;size_t size;};voidlist_string_insert(structlist_int *p,stringvalue);intlist_string_sort(structlist_int *p);boollist_string_empty(structlist_int *p);...

兩者之間就是型別的差別。所以很多時間,在C語言中我們就用巨集來替代它的型別,如下:

12345678910111213141516 //! code-3#define LIST_DECLARE(TYPE) structlist_##TYPE##_item { TYPE## value; structlist_##TYPE##_item *next; };structlist_##TYPE { structlist_##TYPE##_item *head; size_t size;};voidlist_##TYPE##_insert(struct list_##TYPE *p, ##TYPE## value); intlist_##TYPE##_sort(struct list_##TYPE *p); boollist_##TYPE##_empty(struct list_##TYPE *p); ...

然後在標頭檔案中是這樣定義list<double>的:

123 //! code-4LIST_DECLARE(double)

所以,泛型產生冗餘程式碼是無法避免的,至少用C來做這樣的泛型也是無法避免的。

既然無法避免的,那就看看怎麼儘可能以避免上述的問題。在《Effective C++》中有一章節專門提到:不要在模板中使用不必要的引數。因為每一個不同的引數編譯器都會為之生成一套相應的程式碼。

如果程式碼中只有一種資料型別,就算用該型別定義了多個變數,編譯器是不是隻會生成一套相關的程式碼?(應該是這樣的)。

寫個例子對比一下:(省略不必要的程式碼)

test1.cpp,裡面只有map<int, string>,但定義了m1, m2, m3。

123456789 //! code-5map<int,string>m1;map<int,string>m2;map<int,string>m3;m1.insert(std::make_pair(1,"hello"));m2.insert(std::make_pair(1,"hi"));m3.insert(std::make_pair(1,"lichunjun"));

test2.cpp,與test1.cpp相比,裡面有三個型別:

123456789 //! code-6map<int,string>m1;map<int,double>m2;map<int,int>m3;m1.insert(std::make_pair(1,"hello"));m2.insert(std::make_pair(1,1.2));m3.insert(std::make_pair(1,44));

結果,編譯出來的可執行檔案大小比較:

123 [hevake_lcj@Hevake tmp]$ll test1 test2-rwxrwxr-x.118784Mar1922:01test1-rwxrwxr-x.135184Mar1922:03test2

test2比test1大一倍,原因不用多說。

還有一個問題:指標是不是被認為是一個型別?

上面的list<int>與list<string>不能共用同一套程式碼,根據的原因是因為int與string這兩種型別在空間大小與賦值的方式上都是不同的。所以,必須生成兩套程式碼來實現。

而指標,不管是什麼指標,它們都是一樣的。我們可以用void*代表所有的指標型別。

於是我們將上面的程式碼改改,再測試一下:

123456789 //! code-7map<int,string*>m1;map<int,string*>m2;map<int,string*>m3;m1.insert(std::make_pair(1,newstring("hello")));m2.insert(std::make_pair(1,newstring("hi")));m3.insert(std::make_pair(1,newstring("lichunjun")));

123456789 //! code-8map<int,string*>m1;map<int,double*>m2;map<int,int*>m3;m1.insert(std::make_pair(1,newstring("hello")));m2.insert(std::make_pair(1,newdouble(1.2)));m3.insert(std::make_pair(1,newint(44)));

結果是這樣的:

12 -rwxrwxr-x.118736Mar1923:05test1-rwxrwxr-x.135136Mar1923:05test2

預期的結果test1與test2相差不多,但從結果上看並沒有什麼優化,結果有點令人失望~

思考:C++有沒有什麼引數可以優化這個?

如果沒有,為了節省空間,我們只能將所有的指標統一定義成void*型別了,在使用時再強制轉換。

12345678910111213 //! code-9map<int,void*>m1;map<int,void*>m2;map<int,void*>m3;m1.insert(std::make_pair(1,newstring("hello")));m2.insert(std::make_pair(1,newdouble(1.2)));m3.insert(std::make_pair(1,newint(44)));cout<<*static_cast<string*>(m1[1])<<endl;cout<<*static_cast<double*>(m2[1])<<endl;cout<<*static_cast<int*>(m3[1])<<endl;

如上程式碼是將code-8的基礎上,將所有的指定都定義成了void*,在使用的時候用static_cast進行強制轉換成對應的指標型別。

如此得到的程式碼大小與code-7的比較,只多了16個位元組。

但這種做法是很不可取的,必須用void*指標之後,編譯器不再對型別進行檢查,很容易把型別搞混淆。

最好還是編譯器支援指標泛型的優化吧!