詳解C++ 前置聲明
前置聲明是C/C++開(kāi)發(fā)中比較常用的技巧,主要用在三種情形:
- 變量/常量,例如
extern int var1
;; - 函數(shù),例如
void foo();
,注意類(lèi)的成員函數(shù)無(wú)法單獨(dú)做前置聲明; - 類(lèi),例如
class Foo;
,也可以前置聲明模板類(lèi):template class<typename T1, int SIZE>Foo;
。如果類(lèi)包含在名字空間中,需在名字空間內(nèi)做前置聲明:namespace tlanyan {class Foo;};
,而不能這樣:class tlanyan::Foo;
。
前置聲明作用
根據(jù)其用途,前置聲明的主要作用為:
- 避免重復(fù)定義變量;
- 避免引入函數(shù)定義/聲明文件,從而函數(shù)文件發(fā)生更改時(shí)不會(huì)重新編譯依賴(lài)文件;
- 解決循環(huán)依賴(lài)問(wèn)題。
前兩種用途好理解,第三種稍微復(fù)雜點(diǎn),但卻是前置聲明最重要的用途。其解決類(lèi)A包含類(lèi)B,同時(shí)類(lèi)B包含類(lèi)A的依賴(lài)問(wèn)題。循環(huán)依賴(lài)一般是設(shè)計(jì)層面的問(wèn)題,可通過(guò)接口、引入輔助類(lèi)等手段化解。前置聲明也能解決,只是架構(gòu)上稍微別扭。
不管A和B是否定義在同一個(gè)文件中,c++永遠(yuǎn)無(wú)法解決如下形式的循環(huán)依賴(lài)(后文解釋原因):
// file: A.hpp #include "B.hpp" class A { int id; B b; }; // file: B.hpp #include "A.hpp" class B { ... A a; };
前置聲明解決該問(wèn)題需要與指針配合,轉(zhuǎn)換成另一種形式。要點(diǎn)如下:
- 至少將某類(lèi)的變量類(lèi)型轉(zhuǎn)換成指針,例如A中將B轉(zhuǎn)成B*;
- 類(lèi)A中對(duì)B使用前置聲明;
- 類(lèi)A的定義文件中移除對(duì)類(lèi)B文件的包含(做了包含保護(hù)則可忽略)。
使用前置聲明后,以下是一種可行的解決形式(兩個(gè)類(lèi)均使用了前置聲明):
// file: A.hpp //3. 移除對(duì)B的包含(使用了#pragma once或者#ifndef B_HPP等保護(hù)措施則無(wú)必要) // 2. 前置聲明類(lèi)B class B; class A { int id; // 1. 成員變量轉(zhuǎn)換成指針 B* b; }; // file: B.hpp // 3. 移除對(duì)A的包含(有包含保護(hù)則非必要) // 2. 前置聲明類(lèi)A class B { ... // 1. 成員變量轉(zhuǎn)換成指針 A* a; };
深入前置聲明
如果你有其他編程語(yǔ)言的經(jīng)驗(yàn),會(huì)發(fā)現(xiàn)c++有點(diǎn)怪異:Java/C#/Python/PHP等語(yǔ)言可以輕松做到循環(huán)引用,無(wú)需使用類(lèi)似的前置聲明技巧。這不禁讓人思考:C++為何必須要用前置聲明才能化解?
原因在于C++定義對(duì)象有兩種方式:一種是A a形式,a即對(duì)象,調(diào)用成員變量或函數(shù)用.,對(duì)象在棧中分配;另一種是A* a,a是指針,調(diào)用成員變量或函數(shù)用->,其指向地址存儲(chǔ)實(shí)際對(duì)象,對(duì)象在堆中分配。
分配對(duì)象需要知道具體的內(nèi)存大小,但以下形式我們不能確定類(lèi)A和類(lèi)B對(duì)象的大?。?/p>
class A { B b; }; class B { A a; };
對(duì)于這個(gè)簡(jiǎn)單例子,你可以直觀認(rèn)為A和B占用同樣的內(nèi)存,例如1字節(jié),但也可以是2字節(jié),3字節(jié)等;根據(jù)內(nèi)存對(duì)齊要求,一般是4字節(jié),8字節(jié)等。無(wú)論哪種情況,編譯器無(wú)法確定其對(duì)象占用內(nèi)存,便會(huì)報(bào)錯(cuò)停止編譯。所以你應(yīng)該知道為什么C++永遠(yuǎn)不應(yīng)該(不能)這樣做了吧?
那為何前置聲明加指針的組合能解決循環(huán)引用問(wèn)題的呢?因?yàn)檎G闆r下,數(shù)據(jù)類(lèi)型指針在同一機(jī)器的編譯器里占同樣的內(nèi)存。指針一般是4或者8個(gè)字節(jié),對(duì)應(yīng)32和64位指針。用了指針,即使有循環(huán)引用,類(lèi)的大小也能輕易的確定下來(lái)。這也是Java/C#/Python/PHP等可以輕松循環(huán)引用的原因:這些語(yǔ)言中,對(duì)象變量其實(shí)都是指針,也意味著對(duì)象變量都是引用傳遞。
如果不移除文件的相互包含,能否省去前置聲明呢?答案是不能,原因如下:
- C++按照一個(gè)個(gè)編譯單元(translation unit)進(jìn)行編譯,如果兩個(gè)文件互相包含且沒(méi)有#pragma once等包含保護(hù)措施,則會(huì)出現(xiàn)遞歸包含,編譯器報(bào)錯(cuò);
- 如果兩個(gè)頭文件都有文件包含保護(hù),編譯A時(shí)會(huì)把B包含進(jìn)來(lái),但因?yàn)锽包含了A,A中的包含保護(hù)生效,導(dǎo)致B文件內(nèi)的內(nèi)容實(shí)際未引入A,于是報(bào)B為未知符號(hào)的錯(cuò)誤。
總的來(lái)說(shuō),不管是否移除對(duì)方的頭文件,前置聲明都是必須的。實(shí)踐中為了避免文件變動(dòng)時(shí)重新編譯的耗費(fèi),移除不必要的頭文件是一個(gè)好習(xí)慣。
以上就是詳解C++ 前置聲明的詳細(xì)內(nèi)容,更多關(guān)于C++ 前置聲明的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談stringstream 的.str()正確用法和清空操作
下面小編就為大家?guī)?lái)一篇淺談stringstream 的.str()正確用法和清空操作。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12DSP中浮點(diǎn)轉(zhuǎn)定點(diǎn)運(yùn)算--舉例及編程中的心得
本文主要講解DSP浮點(diǎn)轉(zhuǎn)定點(diǎn)運(yùn)算舉例及編程中的心得 ,具有參考價(jià)值,需要的朋友可以參考一下。2016-06-06C語(yǔ)言實(shí)現(xiàn)數(shù)學(xué)表達(dá)式運(yùn)算
這篇文章主要為大家詳細(xì)介紹了c語(yǔ)言實(shí)現(xiàn)數(shù)學(xué)表達(dá)式運(yùn)算,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11關(guān)于STL中l(wèi)ist容器的一些總結(jié)
list就是數(shù)據(jù)結(jié)構(gòu)中的雙向鏈表(根據(jù)sgi stl源代碼),因此它的內(nèi)存空間是不連續(xù)的,通過(guò)指針來(lái)進(jìn)行數(shù)據(jù)的訪(fǎng)問(wèn),這個(gè)特點(diǎn)使得它的隨即存取變的非常沒(méi)有效率,因此它沒(méi)有提供[]操作符的重載2013-09-09c++實(shí)現(xiàn)逐行讀取配置文件寫(xiě)入內(nèi)存的示例
這篇文章主要介紹了c++實(shí)現(xiàn)逐行讀取配置文件寫(xiě)入內(nèi)存的示例,需要的朋友可以參考下2014-05-05淺談C++ 類(lèi)的實(shí)例中 內(nèi)存分配詳解
下面小編就為大家?guī)?lái)一篇淺談C++ 類(lèi)的實(shí)例中 內(nèi)存分配詳解。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12C++入門(mén)(命名空間,缺省參數(shù),函數(shù)重載,引用,內(nèi)聯(lián)函數(shù),auto,范圍for)
這篇文章主要介紹了C++入門(mén)(命名空間,缺省參數(shù),函數(shù)重載,引用,內(nèi)聯(lián)函數(shù),auto,范圍for),這些基礎(chǔ)知識(shí)是學(xué)習(xí)C++最最基礎(chǔ)需要掌握的知識(shí)點(diǎn),需要的朋友可以參考下2021-05-05