一.重復包含頭文件
頭文件重復包含,可能會導致的錯誤包括:變量重定義,類型重定義及其他一些莫名其妙的錯誤。C++提供兩種解決方案,分別是#ifndef和#pragma once
#ifndef _SOME_FILE_H_ #pragma once
#define _SOME_FILE_H_ ...//一些聲明語句
...//一些聲明語句
#endif
第一種方式:通過這種預處理實現唯一檢查。預處理首先測試_SOME_FILE_H_變量是否未定義,如果_SOME_FILE_H_未定義,那么#ifndef測試成功,跟在#ifndef后面的所有行都被執行,直到發現#endif。相反,如果_SOME_FILE_H_已定義,那么#ifndef指示測試為假,該指示和#endif指示間的代碼都被忽略。
為了保證頭文件在給定的源文件中只處理一次,首先檢測#ifndef。第一次處理頭文件時,測試會成功,因為_SOME_FILE_H_還未定義,下一條語句定義了_SOME_FILE_H_,那樣的話,如果編譯的文件恰好又一次包含了該頭文件,#ifndef指示會發現_SOME_FILE_H_已經定義,並且忽略該頭文件的剩余部分。
當沒有兩個頭文件定義和使用同名的預處理器常量時,這個策略相當有效。當有兩個文件使用同一個宏,這個策略就失效了。有兩種方案解決:
1.可以為定義在頭文件里的實體(如類)命名預處理器變量,來避免預處理器變量重名的問題。如,一個程序只能含有一個名為sales_item的類,通過使用類名來組成頭文件和預處理器變量的名字,可以使得很可能只有一個文件將會使用該預處理器變量
2.為了保證宏的唯一性,可以采用google提供的解決方案,在定義宏時,宏名基於其所在的項目源代碼數的全路徑命名,宏命名格式為:_<PROJECT><PATH>_<FILE>_H_
在定義#ifndef測試宏時,宏名最好采用全大寫字母表示
在定義測試宏時,最好采用google提供的解決方案
第二種方式:#pragma once
這種方式一般由編譯器提供,只要在頭文件的最開始加入這條指令就能夠保證頭文件只被編譯一次。#pragma once用來防止某個頭文件被多次include,#ifndef方式用來防止某個宏被多次定義。#pragma once是編譯相關,即在這個編譯系統上使用,但在其它編譯系統上不一定能用,也就是說移植性差,不過現在基本上已經是每個編譯器都有這個定義了。
針對#pragma once,GCC已經取消對其的支持了,而微軟的VC++卻依然支持
如果寫的程序需要跨平台,最好使用#ifndef方式,而避免使用#pragma once方式
二.相互包含問題
CA類包含CB類的實例,而CB類也包含CA類的實例。代碼如下
//A.h實現CA類的定義 //B.h實現CB類的定義
#include "B.h" #include "A.h" int main()
class CA class CB {
{ { CA instanceA;
public: public: return 0;
int iData; int iData; }
CB instaceB; CA instaceA;
}; };
編譯出錯分析:
CA類定義時使用CB類的定義,CB類定義時使用CA類的定義,遞歸定義
A.h包含了B.h,B.h包含了A.h,也存在遞歸包含的問題
其實,無論是結構體的遞歸定義,還是類的遞歸定義,最后都歸於一個問題,C/C++采用靜態編譯模型,在程序運行時,結構或類大小都會在編譯后確定。程序要正確編譯,編譯器必須知道一個結構或結構所占用的空間大小,否則編譯器就會報出奇怪的編譯錯誤。
解法:
1.向前聲明實現
//A.h實現CA類的定義 //B.h實現CB類的定義
class CB//前向聲明CB類 #include "A.h" #include "B.h"
class CA class CB int main()
{ { {
public: public: CA instaceA;
int iData; int iData; return 0;
CB* pinstaceB; CA instaceA; }
}; };
前向聲明實現方式的主要實現原則
主函數只需要包含B.h就可以,因為B.h中包含了A.h
A.h中不需要包含B.h,但要聲明class CB,在避免死循環的同時也成功引用了CB
包含class CB聲明,而沒有包含頭文件B.h,這樣只能聲明CB類型的指針,而不能實例化
2.friend聲明定義
//A.h實現CA類的定義 //B.h實現CB類的定義
class CB//前向聲明CB類 #include "A.h" #include "B.h"
class CA class CB int main()
{ { {
public: public: CA instaceA;
friend classCB; //友元類聲明
int iData; int iData; return 0;
CB* pinstaceB; CA instaceA; }
}; };
friend友元聲明實現說明:
主函數只需要包含B.h就可以,因為B.h中包含了A.h
A.h不需要包含B.h,但要聲明class CB,在避免死循環的同時也成功引用了CB
class CA包含class CB友元聲明,而沒有包含頭文件B.h,這樣只能聲明CB類型的指針,而不能實例化
無論是前向聲明還是friend友元實現,有一點是肯定的,即最多只能有一個類可以定義實例。同樣頭文件包含也是一件很麻煩的事情,再加上頭文件中常常出現的宏定義,各種宏定義的展開是非常耗時間的
類或結構體遞歸定義實現應遵循兩個原則
1.如果可以不包含頭文件,那就不要包含了。這時候前置聲明可以解決問題的。如果使用的僅僅是一個類的指針,沒有使用這個類的具體對象,也沒有訪問到類的具體成員,那么前置聲明就可以了。因為指針這一數據類型的大小是特定的,編譯器可以獲知。
2.盡量在cpp文件中包含頭文件,而非在頭文件。假設類A的一個成員是一個指向類B的指針,在類A的頭文件中使用了類B的前置聲明並編譯成功,那么在A的實現中需要訪問B的具體成員,因此需要包含頭文件,那么我們應該在類A的實現部分包含類B的頭文件而非聲明部分。