標籤:
下面有3個小問題,作為C++ Beginner你一定要知道錯在哪裡了。
1、衍生類別到基類的引用或指標轉換一定“完美”存在?
一般情況,你很可能會認為:衍生類別對象的引用或指標轉換為基類對象的引用或指標是一件很正常的事。那要是不一般情況呢?請看下面這個例子:
class Person{public: Person(const string& str = "Normal Person") : ID(str) {} string ID; //作為一般的人身份是“普通人”,作為學生身份是“學生”};class Student : private Person{public: Student(const string& str = "Student") : Person(str) {}};
仍然是以普通人和學生作為例子,學生是private派生自普通人的。ID表示身份狀態(我將它聲明為public的,完全是為了方便,我仍然強烈建議聲明為private)。
void PrintID(const Person& p) //列印ID{ cout << p.ID << endl;}
我有下面這樣的調用:
Person p;Student s;PrintID(p); //好的,沒有問題PrintID(s); //編譯出錯
好的,我已經注釋出來了。編譯出錯,出錯資訊是:error C2243: “類型轉換”: 從“Student *”到“const Person &”的轉換存在,但無法訪問。是不是有點跟你想的不大一樣呢?
分析:
很多時候,我們都預設public繼承,public繼承勾勒出一種is-a的關系,就是衍生類別是一種基類,從這個語義上理解似乎從衍生類別轉換為基類的自動的(引用或指標方式)。我這裡說的是private繼承,private繼承勾勒出一種“根據某物實現出”的關係。假設D以private派生於B,意思是D可以根據B對象實現得到。編譯器不會自動將衍生類別對象轉換為基類對象。
另外一點疑問,你可能注意到了PrintID如果傳入的是Student對象,似乎訪問了私人成員ID(private繼承),是不是這個原因導致編譯錯誤呢?我們將其改為下面這樣:
void PrintID(const Person& p) //列印ID{ cout << "ID Infor" << endl;// cout << p.ID << endl;}
同樣編譯錯誤。所以,要注意:衍生類別到基類的引用或指標轉換一定“完美”存在,前提是public繼承,不能是private或protected繼承。
說明:還要補充說明一下,在類的繼承層次中public為主,private有時候在設計上可以提供一點“便利”,protected我暫時還不知道有什麼好的用處。
2、關鍵字typename導致的編譯時間錯誤。
這個問題,可能很多人都知道,也許像我一樣知道的“不全”。首先需要明確一點是:關鍵字typename與class在聲明template型別參數時完全一樣。我其實要說的是typename泛型(模板)中的使用,它可是大有所為的。
template內出現的名稱如果依賴於某個template參數,稱之為從屬名稱。如果從屬名稱在class內呈嵌套狀,稱之為嵌套從屬名稱。
template <typename T>void PrintElement(const T& vec){ T::const_iterator* x; //const_iterator是個嵌套從屬名稱...}
我們可能認為聲明了一個指標,指向一個T::const_iterator。實際上,T::const_iterator也可能是個名字為const_iterator的static變數,上面那行代碼就定義了一個乘法。所以,嵌套從屬名稱可能導致解析困難。
C++解析器有個簡單的規則:在template中遇到一個嵌套從屬名稱,它會假設這個名稱不是個類型,這是預設情況,除非你用關鍵字typename主動告訴編譯器。
template <typename T>void PrintElement(const T& vec){ typename T::const_iterator iter; //這樣聲明就對了...}
這塊知識,基本每個人都知道。我其實想說的是,使用typename時,有幾個小點注意一下,因為typename的位置不僅在函數內。比如:
template <typename T>void Print(typename T::const_iterator iter) //形參表一定要使用typename
下面再舉個複雜一點的例子:
template <class T>class Base {public: class Nested { public: Nested(int a) : x(a) {} private: int x; };};template<class T>class Derived : public Base<T>::Nested //繼承列表,你不能使用typename{public: explicit Derived(int a) : Base<T>::Nested(a){} //建構函式初始化列表,你不能使用typename};
注意,代碼中注釋的部分,就是typename兩個不能使用的地方。我再擴充一下這個問題:
template<class T>void func(const typename Base<T>::Nested& test) //使用typename,很好的{ cout << "Very Good" << endl; }
我想問的是:Derived<int> d(5);怎麼調用那個函數呢?正確是策略是:
func<int> (d); //正確的func(d); //錯誤的
錯誤的用法,編譯器會提醒你:無法解析T。
3、建構函式、解構函式能否為虛函數?建構函式、解構函式能否調用虛函數?
這個小問題,真是源遠流長了。標準答案是不是都有了呢?我再不耐其煩的說一下。(我沒有把各家說法收集全,就是說下自己的理解)
(1)建構函式、解構函式能否為虛函數?
建構函式不能為虛函數。
回答:有好幾個點可以說說,我說的不全。
1、虛函數的作用主要是為了執行期根據Base指標定址到“正確”的函數。既然是執行期,必然已經跨過編譯期了,那勢必對象都建立起來了,建構函式是初始化用的,那要建構函式何用。
2、如果我沒記錯的話,建構函式有一個功能是建立vptr,注意不是vtbl(編譯期搞定的),假設是虛函數,誰來建立vptr,此時建構函式應該在vtbl裡待著呢。
3、虛函數是為了衍生類別對其進行改造的,但建構函式不能被繼承,談何改造。
等等…
解構函式能不能為虛函數?
如果像STL那樣堅決不做衍生類別,我勸你別把它當虛函數,vptr好歹也是4個位元組的儲存呢。如果你要它作為衍生類別,我勸你一定要設定為虛函數,否則記憶體泄露會找上你。
(2)建構函式、解構函式能否調用虛函數?
其實這個問題,我一直都沒明白有啥意義。建構函式、解構函式調用虛函數是可以的,可是調用的是所屬類的版本。
目錄:
C++中幾個值得分析的小問題(1)
C++中幾個值得分析的小問題(2)
C++中幾個值得分析的小問題(2)