首先,我們為什麼要包括標頭檔?問題的回答很簡單,通常是我們需要獲得某個類型的定義(definition)。那麼接下來的問題就是,在什麼情
況下我們才需要類型的定義,在什麼情況下我們只需要聲明就足夠了?問題的回答是當我們需要知道這個類型的大小或者需要知道它的函數簽名的時候,我們就需要
獲得它的定義。
假設我們有類型A和類型C,在哪些情況下在A需要C的定義:
- A繼承至C
- A有一個類型為C的成員變數
- A有一個類型為C的指標的成員變數
- A有一個類型為C的引用的成員變數
- A有一個類型為std::list<C>的成員變數
- A有一個函數,它的簽名中參數和傳回值都是類型C
- A有一個函數,它的簽名中參數和傳回值都是類型C,它調用了C的某個函數,代碼在標頭檔中
- A有一個函數,它的簽名中參數和傳回值都是類型C(包括類型C本身,C的參考型別和C的指標類型),並且它會調用另外一個使用C的函數,代碼直接寫在A的標頭檔中
- C和A在同一個名字空間裡面
- C和A在不同的名字空間裡面
1,沒有任何辦法,必須要獲得C的定義,因為我們必須要知道C的成員變數,成員函數。
2,需要C的定義,因為我們要知道C的大小來確定A的大小,但是可以使用Pimpl慣用法來改善這一點,詳情請
看Hurb的Exceptional C++。
3,4,不需要,前置聲明就可以了,其實3和4是一樣的,引用在物理上也是一個指標,它的大小根據平台不同,可能是32位也可能是64位,反正我們不需要知道C的定義就可以確定這個成員變數的大小。
5,不需要,有可能老式的編譯器需要。標準庫裡面的容器像list, vector,map,
在包括一個list<C>,vector<C>,map<C, C>類型的成員變數的時候,都不需要C的定義。因為它們內部其實也是使用C的指標作為成員變數,它們的大小一開始就是固定的了,不會根據模版參數的不同而改變。
6,不需要,只要我們沒有使用到C。
7,需要,我們需要知道調用函數的簽名。
8,8的情況比較複雜,直接看代碼會比較清楚一些。
C& doToC(C&);
C& doToC2(C& c) ...{return doToC(c);};
從上面的代碼來看,A的一個成員函數doToC2調用了另外一個成員函數doToC,但是無論是doToC2,還是doToC,它們的的參數和返
回類型其實都是C的引用(換成指標,情況也一樣),引用的賦值跟指標的賦值都是一樣,無非就是整形的賦值,所以這裡即不需要知道C的大小也沒有調用C的任
何函數,實際上這裡並不需要C的定義。
但是,我們隨便把其中一個C&換成C,比如像下面的幾種樣本:
1.
C& doToC(C&);
C& doToC2(C c) ...{return doToC(c);};
2.
C& doToC(C);
C& doToC2(C& c) {return doToC(c);};
3.
C doToC(C&);
C& doToC2(C& c) {return doToC(c);};
4.
C& doToC(C&);
C doToC2(C& c) {return doToC(c);};
無論哪一種,其實都隱式包含了一個拷貝建構函式的調用,比如1中參數c由拷貝建構函式產生,3中doToC的傳回值是一個由拷貝建構函式產生的匿名對象。因為我們調用了C的拷貝建構函式,所以以上無論那種情形都需要知道C的定義。
9和10都一樣,我們都不需要知道C的定義,只是10的情況下,前置聲明的文法會稍微複雜一些。
最後給出一個完整的例子,我們可以看到在兩個不同名字空間的類型A和C,A是如何使用前置聲明來取代直接包括C的標頭檔的:
A.h
#pragma once
#include <list>
#include <vector>
#include <map>
#include <utility>
//不同名字空間的前置聲明方式
namespace test1
...{
class C;
}
namespace test2
...{
//用using避免使用完全限定名
using test1::C;
class A
...{
public:
C useC(C);
C& doToC(C&);
C& doToC2(C& c) ...{return doToC(c);};
private:
std::list<C> _list;
std::vector<C> _vector;
std::map<C, C> _map;
C* _pc;
C& _rc;
};
}
C.h
#ifndef C_H
#define C_H
#include <iostream>
namespace test1
...{
class C
...{
public:
void print() ...{std::cout<<"Class C"<<std::endl;}
};
}
#endif // C_H