淺析C++中前置聲明的應(yīng)用與陷阱
前置聲明的使用
有一定C++開(kāi)發(fā)經(jīng)驗(yàn)的朋友可能會(huì)遇到這樣的場(chǎng)景:兩個(gè)類(lèi)A與B是強(qiáng)耦合關(guān)系,類(lèi)A要引用B的對(duì)象,類(lèi)B也要引用類(lèi)A的對(duì)象。好的,不難,我的第一直覺(jué)讓我寫(xiě)出這樣的代碼:
// A.h
#include "B.h"
class A
{
public:
A(void);
virtual ~A(void);
};
//A.cpp
#include "A.h"
A::A(void)
{
}
A::~A(void)
{
}
// B.h
#include "A.h"
class B
{
A a;
public:
B(void);
~B(void);
};
// B.cpp
#include "B.h"
B::B(void)
{
}
B::~B(void)
{
}
好的,完成,編譯一下A.cpp,不通過(guò)。再編譯B.cpp,還是不通過(guò)。編譯器都被搞暈了,編譯器去編譯A.h,發(fā)現(xiàn)包含了B.h,就去編譯B.h。編譯B.h的時(shí)候發(fā)現(xiàn)包含了A.h,但是A.h已經(jīng)編譯過(guò)了(其實(shí)沒(méi)有編譯完成,可能編譯器做了記錄,A.h已經(jīng)被編譯了,這樣可以避免陷入死循環(huán)。編譯出錯(cuò)總比死循環(huán)強(qiáng)點(diǎn)),就沒(méi)有再次編譯A.h就繼續(xù)編譯。后面發(fā)現(xiàn)用到了A的定義,這下好了,A的定義并沒(méi)有編譯完成,所以找不到A的定義,就編譯出錯(cuò)了。提示信息如下:
1>d:/vs2010/test/test/a.h(5): error C2146: syntax error : missing ';' before identifier 'b'
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
那怎么辦?有辦法,C++為我們提供了前置聲明。前置聲明是什么?舉個(gè)形象點(diǎn)的例子,就是我要蓋一個(gè)屋子(CHOuse),光有屋子還不行啊,我還得有床(CBed)。但是屋子還沒(méi)蓋好,總不能先買(mǎi)床吧,床的大小我定了,改天買(mǎi)。先得把房子蓋好,蓋房子的時(shí)候我先給床留個(gè)位置,等房子蓋好了,我再?zèng)Q定買(mǎi)什么樣的床。前置聲明就是我在聲明一個(gè)類(lèi)(CHouse)的時(shí)候,用到了另外一個(gè)類(lèi)的定義(CBed),但是CBed還沒(méi)有定義呢,而且我還先不需要CBed的定義,只要知道CBed是一個(gè)類(lèi)就夠了。那好,我就先聲明類(lèi)CBed,告訴編譯器CBed是一個(gè)類(lèi)(不用包含CBed的頭文件):
class CBed;
然后在CHouse中用到CBed的,都用CBed的指針類(lèi)型代(因?yàn)橹羔橆?lèi)型固定大小的,但是CBed的大小只用知道了CBed定義才能確定)。等到要實(shí)現(xiàn)CHouse定義的時(shí)候,就必須要知道CBed的定義了,那是再包好CBed的頭文件就行了。
前置聲明有時(shí)候很有用,比如說(shuō)兩個(gè)類(lèi)相互依賴(lài)的時(shí)候要。還有前置聲明可以減少頭文件的包含層次,減少出錯(cuò)可能。上面說(shuō)的例子。
// House.h
class CBed; // 蓋房子時(shí):現(xiàn)在先不買(mǎi),肯定要買(mǎi)床的
class CHouse
{
CBed* bed; // 我先給床留個(gè)位置
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed();
};
// House.cpp
#include "Bed.h"
#include "House.h" // 等房子開(kāi)始裝修了,要買(mǎi)床了
CHouse::CHouse(void)
{
bed = new CBed(); // 把床放進(jìn)房子
}
CHouse::~CHouse(void)
{
}
void CHouse::GoToBed()
{
bed->Sleep();
}
// Bed.h
class CBed
{
public:
CBed(void);
~CBed(void);
void Sleep();
};
// Bed.cpp
#include "Bed.h"
CBed::CBed(void)
{
}
CBed::~CBed(void)
{
}
void CBed::Sleep()
{
}
前置聲明中的陷阱
注意這里有陷阱:
1、CBed* bed;必須用指針或引用
引用版本:
// House.h
class CBed; // 蓋房子時(shí):現(xiàn)在先不買(mǎi),肯定要買(mǎi)床的
class CHouse
{
CBed& bed; // 我先給床留個(gè)位置
// CBed bed; // 編譯出錯(cuò)
public:
CHouse(void);
CHouse(CBed& bedTmp);
virtual ~CHouse(void);
void GoToBed();
};
// House.cpp
#include "Bed.h"
#include "House.h" // 等房子開(kāi)始裝修了,要買(mǎi)床了
CHouse::CHouse(void)
: bed(*new CBed())
{
CBed* bedTmp = new CBed(); // 把床放進(jìn)房子
bed = *bedTmp;
}
CHouse::CHouse(CBed& bedTmp)
: bed(bedTmp)
{
}
CHouse::~CHouse(void)
{
delete &bed;
}
void CHouse::GoToBed()
{
bed.Sleep();
}
2、不能在CHouse的聲明中使用CBed的方法
使用了未定義的類(lèi)型CBed;
bed->Sleep的左邊必須指向類(lèi)/結(jié)構(gòu)/聯(lián)合/泛型類(lèi)型
class CBed; // 蓋房子時(shí):現(xiàn)在先不買(mǎi),肯定要買(mǎi)床的
class CHouse
{
CBed* bed; // 我先給床留個(gè)位置
// CBed bed; // 編譯出錯(cuò)
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed()
{
bed->Sleep(); // 編譯出錯(cuò),床都沒(méi)買(mǎi),怎么能睡
}
};
3、在CBed定義之前調(diào)用CBed的析構(gòu)函數(shù)
// House.h
class CBed; // 蓋房子時(shí):現(xiàn)在先不買(mǎi),肯定要買(mǎi)床的
class CHouse
{
CBed* bed; // 我先給床留個(gè)位置
// CBed bed; // 編譯出錯(cuò)
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed();
void RemoveBed()
{
delete bed; // 我不需要床了,我要把床拆掉。還沒(méi)買(mǎi)怎么拆?
}
};
// House.cpp
#include "Bed.h"
#include "House.h" // 等房子開(kāi)始裝修了,要買(mǎi)床了
CHouse::CHouse(void)
{
bed = new CBed(); // 把床放進(jìn)房子
}
CHouse::~CHouse(void)
{
int i = 1;
}
void CHouse::GoToBed()
{
bed->Sleep();
}
// Bed.h
class CBed
{
int* num;
public:
CBed(void);
~CBed(void);
void Sleep();
};
// Bed.cpp
#include "Bed.h"
CBed::CBed(void)
{
num = new int(1);
}
CBed::~CBed(void)
{
delete num; // 調(diào)用不到
}
void CBed::Sleep()
{
}
//main.cpp
#include "House.h"
int main()
{
CHouse house;
house.RemoveBed();
}
前置聲明解決兩個(gè)類(lèi)的互相依賴(lài)
接下來(lái),給出開(kāi)篇第一個(gè)問(wèn)題的答案:
// A.h
class B;
class A
{
B* b;
public:
A(void);
virtual ~A(void);
};
//A.cpp
#include "B.h"
#include "A.h"
A::A(void)
{
b = new B;
}
A::~A(void)
{
}
// B.h
class A;
class B
{
A a;
public:
B(void);
~B(void);
};
// B.cpp
#include "A.h"
#include "B.h"
B::B(void)
{
a = New A;
}
B::~B(void)
{
}
前置聲明在友元類(lèi)方法中的應(yīng)用
《C++ Primer 4Edition》在類(lèi)的友元一章節(jié)中說(shuō)到,如果在一個(gè)類(lèi)A的聲明中將另一個(gè)類(lèi)B的成員函數(shù)聲明為友元函數(shù)F,那么類(lèi)A必須事先知道類(lèi)B的定義;類(lèi)B的成員函數(shù)F聲明如果使用類(lèi)A作為形參,那么也必須知道類(lèi)A的定義,那么兩個(gè)類(lèi)就互相依賴(lài)了。要解決這個(gè)問(wèn)題必須使用類(lèi)的前置聲明。例如:
// House.h
#include "Bed.h"
class CHouse
{
friend void CBed::Sleep(CHouse&);
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed();
void RemoveBed()
{
}
};
// House.cpp
#include "House.h"
CHouse::CHouse(void)
{
}
CHouse::~CHouse(void)
{
int i = 1;
}
void CHouse::GoToBed()
{
}
// Bed.h
class CHouse;
class CBed
{
int* num;
public:
CBed(void);
~CBed(void);
void Sleep(CHouse&);
};
// Bed.cpp
#include "House.h"
CBed::CBed(void)
{
num = new int(1);
}
CBed::~CBed(void)
{
delete num;
}
void CBed::Sleep(CHouse& h)
{
}
相關(guān)文章
C++標(biāo)準(zhǔn)模板庫(kù)string類(lèi)的介紹與使用講解
今天小編就為大家分享一篇關(guān)于C++標(biāo)準(zhǔn)模板庫(kù)string類(lèi)的介紹與使用講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12C++ Boost Lockfree超詳細(xì)講解使用方法
Boost是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)。Boost庫(kù)是一個(gè)可移植、提供源代碼的C++庫(kù),作為標(biāo)準(zhǔn)庫(kù)的后備,是C++標(biāo)準(zhǔn)化進(jìn)程的開(kāi)發(fā)引擎之一,是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)2022-11-11C++??STL?_?Vector使用及模擬實(shí)現(xiàn)
這篇文章主要介紹了C++ STL_Vector使用及模擬實(shí)現(xiàn),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08c++連接mysql數(shù)據(jù)庫(kù)的兩種方法(ADO連接和mysql api連接)
現(xiàn)在正做一個(gè)接口,通過(guò)不同的連接字符串操作不同的數(shù)據(jù)庫(kù)。要用到mysql數(shù)據(jù)庫(kù),C++連接mysql有2種方法:利用ADO連接、利用mysql自己的api函數(shù)進(jìn)行連接,下面看看如何用吧2013-12-12C/C++函數(shù)參數(shù)聲明解析int?fun()?與?int?fun(void)?的區(qū)別講解
C++中int fun()和int fun(void)的區(qū)別在于函數(shù)參數(shù)的聲明方式,前者默認(rèn)允許任意參數(shù),而后者表示沒(méi)有參數(shù),通過(guò)清晰的實(shí)例源代碼,詳細(xì)解釋了它們?cè)诤瘮?shù)聲明和調(diào)用中的不同之處,這篇文章介紹了C/C++函數(shù)參數(shù)聲明int?fun()與int?fun(void)的差異,需要的朋友可以參考下2024-01-01Linux UDP服務(wù)端和客戶(hù)端程序的實(shí)現(xiàn)
這篇文章主要介紹了Linux UDP服務(wù)端和客戶(hù)端程序的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05C/C++如何獲取當(dāng)前系統(tǒng)時(shí)間的實(shí)例詳解
這篇文章主要介紹了 C/C++如何獲取當(dāng)前系統(tǒng)時(shí)間的實(shí)例詳解的相關(guān)資料,這里提供了幾種實(shí)現(xiàn)方法,幫助大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-08-08