本專欄文章列表
一、何為面向對象
二、C語言也能實現面向對象
三、C++中的不優雅特性
四、解決封裝,避免接口
五、合理使用模板,避免代碼冗余
六、C++也能反射
七、單例模式解決靜態成員對象和全局對象的構造順序難題
八、更為高級的預處理器PHP
今天來說一說C++中不優雅的一些問題,C++雖然是面向對象的設計語言,但也有很多缺陷和弊病,我們將會討論如何通過良好的設計解決這些問題。
C++編譯慢已經成為了業界共識,一個大型C++項目甚至要用專用的服務器編譯好久才能完成,Java和.net作為大型開發平台, 卻也沒發現編譯如此緩慢的問題,那麼究竟是什麼,導致了C++編譯難的問題呢?
C++中模板有個很神奇的問題,就是實現和聲明都必須被使用者引用,這段模板代碼才有效,也就是說,模板是在編譯時展開的代碼生成機制。
我們不妨做個實驗,這是類的聲明:
template
class CObject
{
public:
CObject(T k) {obj = k;}
~CObject() {}
T getObj();
private:
T obj;
};
下面是類的實現:
#include "CObject.h"
template
T CObject::getObj(){
return this->obj;
}
主函數中調用:
#include
#include "CObject.h"
using namespace std;
int main(){
CObject Obj(10);
int k = Obj.getObj();
printf("%d\n", k);
return 0;
}
一切看起來是那麼的順利,但是!我的電腦給我顯示如下錯誤信息:
Scanning dependencies of target template_test
[ 50%] Building CXX object CMakeFiles/template_test.dir/src/CObject.cpp.o
[100%] Building CXX object CMakeFiles/template_test.dir/src/main.cpp.o
Linking CXX executable template_test
CMakeFiles/template_test.dir/src/main.cpp.o:在函數‘main’中:
main.cpp:(.text+0x22):對‘CObject::getObj()’未定義的引用
collect2: error: ld returned 1 exit status
make[2]: *** [template_test] 錯誤 1
make[1]: *** [CMakeFiles/template_test.dir/all] 錯誤 2
make: *** [all] 錯誤 2
鏈接器告訴我,我們找不到一個叫做‘CObject::getObj()’的函數,恩?為何,我們不是將類實現鏈接進來了麼?
如果你這樣想就錯了,上網查找解決方案,得到的回復居然是這樣:
#include "CObject.h"
=> #include "CObject.cpp"
omg,那我還不如把兩個文件寫成一個hpp來的方便呢,其實C++也是推薦你這樣做的,理由就是——模板是編譯時,在用到的時候進行代碼展開得到的
如果不這樣做,鏈接器是不會找到對應的代碼的。
那麼也找到了很多大型工程如boost庫,為何編譯緩慢的直接原因,大量的模板展開消耗了巨大的資源,而且模板展開是很不利於代碼復用的,同樣的算法,換一種類型,必須全部編譯,生成新的代碼,並且這類模板生成的代碼,不能提前編譯成二進制庫,這樣的結果就是,項目哪裡改動一點,好多文件重復編譯,造成編譯十分緩慢。
C++的類並沒有很好的將代碼封起來,這和上次講到的GObject對比可以發現,C++的私有變量是一同放置在類的聲明中,而我們知道,一個類的聲明,是會被很多其他類引用的。
那麼,思考我們的C++編譯過程,很多類都引用了一個.h文件,那麼這個.h文件一旦發生更改,那麼所有引用這個文件的cpp文件都將被觸發重復編譯,而我們在實現一個類時,對類的成員函數小修小補是很平常的,但由於封裝的不徹底,那麼我們的項目又將被反復編譯,帶來編譯的速度緩慢。
而且,如果是庫的話,那麼私有成員的更新甚至還會影響用戶使用,非常麻煩。
例如下面這段代碼:
class Test {
public:
Test();
~Test();
void Show();
private:
std::string message;
int pointer;
void formatMessage(std::string&);
};
很明顯,一般的C++類,私有成員都會比公開成員多,那麼私有成員修改一點,哪怕只是一不小心多了個空格,都會帶來這個文件的更新,觸發makefile的重編譯,帶來了低效率。
最新的C++11,引入了眾多的新特性,包括好用的auto關鍵字以及模板元編程特性等,但這些,還是不能彌補反射機制缺失帶來的影響。反射是對象串行化、GUI界面事件響應和根據數據動態調用代碼等技術的核心,缺乏反射機制,會使得C++很多地方十分的不便。
很多大型軟件,如firefox,在實現中,往往搭建了反射框架,供系統使用。但由於C++本身語法的問題,缺乏反射依舊會使得類書寫變得困難。
C++的跨平台性真的不好,甚至很多編譯器上都會出現匪夷所思的問題,例如在不同平台上,基本類型的大小會隨CPU字長而變化,如果有跨平台需求的軟件,最好使用跨平台定義的類型。
C++的結構體中數據往往有內存對齊的問題,有些編譯器還能通過編譯器指令對其設置,這些問題最好還是能避開就避開。
跨平台時,還應小心異常處理的代碼,因為有些版本的C++編譯器對拋出的異常規格並不很遵守規范。
另外,不同平台的寬字符集也是大問題,往往並不能輕松統一,另外MinGW裡貌似就沒有寬字符- -