建構函式初始化成員變數
建構函式初始化成員變數有兩種方法,一種是透過在建構函式中賦值的方式,另外一種是透過成員初始化列表的方式,兩者初始化方式最大的差別就是後者比前者效率高效能好。這是因為C++在執行過程中首先呼叫成員變數的預設建構函式,再進入建構函式中。我們拿實際程式碼來觀察一下。
#include <iostream>#include <list>class Object{public: Object(); ~Object() = default; const class Object &operator=(const class Object &Rhs); bool operator==(const class Object &Rhs);private: int Value;};Object::Object():Value(0){ std::cout << "Object constructor run" << std::endl;}bool Object::operator==(const class Object &Rhs){ if(this->Value == Rhs.Value){ return true; } return false;}const class Object& Object::operator=(const class Object &Rhs){ std::cout << "Object operator= run" << std::endl; if(*this == Rhs){ return *this; } this->Value = Rhs.Value; return *this;}class PthoneNumber{public: PthoneNumber() = default; ~PthoneNumber() = default;private: std::list<int> TelphoneNumbers;};class ABEntry{public: ABEntry(const std::string &Name, const std::string &Address, const std::list<PthoneNumber> &Pthones, const Object &InputObject); ~ABEntry() = default;private: std::string Name; std::string Address; std::list<PthoneNumber> ThePhones; int TheTimesConsult; class Object EmptyObject;};ABEntry::ABEntry(const std::string &Name, const std::string &Address, const std::list<PthoneNumber> &Pthones, const Object &InputObject):Name(Name),Address(Address),ThePhones(Pthones),EmptyObject(InputObject),TheTimesConsult(0){ std::cout << "ABEntry constructor run" << std::endl; //EmptyObject = InputObject;}int main(int argc, char *argv[]){ std::string Name("Lihua"); std::string Addr("Beijing"); std::list<PthoneNumber> Pthones; Object TempObject; ABEntry AddrBookEntry(Name, Addr, Pthones, TempObject); return 0;}
在類ABEntry實現的建構函式中使用成員初始化列表的方式初始化成員變數EmptyObject,而我們在Object類的建構函式和ABEntry建構函式中分別列印一條訊息,現在我們編譯執行看看結果。
C++成員列表初始化執行結果圖
從輸出結果中可以看出在進入ABEntry建構函式之前就已經對EmptyObject成員變數進行了初始化,所以透過成員變數初始化列表初始化成員變數是在進入建構函式之前進行。
我們再來看一下透過在構造函數里面透過賦值的方式對成員變數進行初始化的效果,以此和成員變數初始化列表進行對比。因為Emptyobject成員變數的建構函式中有輸出資訊,只要把Emptyobject透過賦值的方式初始化即可看到效果,我們把ABEntry建構函式修改如下。
ABEntry::ABEntry(const std::string &Name, const std::string &Address, const std::list<PthoneNumber> &Pthones, const Object &InputObject):Name(Name),Address(Address),ThePhones(Pthones),//EmptyObject(InputObject),TheTimesConsult(0){ std::cout << "ABEntry constructor run" << std::endl; EmptyObject = InputObject;}
現在我們重新編譯執行看看效果。
成員列表初始化和賦值初始化執行結果對比圖
從輸出結果我們可以看到總共呼叫了2次Object建構函式,和1次Object賦值運算子,而且賦值運算子在建構函式之後執行。我們的程式碼裡只有在main函數里面定義了1個Object型別變數TempObject會呼叫1次預設的建構函式,那麼多出的1次呼叫建構函式肯定是在呼叫ABEntry建構函式時進行的,也就說明了一個問題,C++先呼叫成員變數的預設建構函式再進入建構函式內進行賦值操作初始化成員變數,所以賦值初始化成員變數和成員變數初始化列表的最大不同點是:在建構函式中透過賦值操作初始化成員變數,會先呼叫成員的預設建構函式再透過賦值初始化成員變數,比初始化成員列表的方式多呼叫1次建構函式和賦值運算子,因此成員變數初始化列表比賦值操作初始化效率高、效能好。
如何建立一個不可複製的類在我們沒有宣告一個複製建構函式和賦值運算子時,C++編譯器會幫我們生成一個預設的複製建構函式和賦值運算子,且生成的複製建構函式和賦值運算子是公有的。如果我們想建立一個不可複製的類該如何做呢?常見的方法有:1、將複製建構函式和賦值運算子變成私有的,2、複製建構函式和賦值運算子變成受保護的,3、類繼承於一個不可複製的父類,該父類沒有任何屬性,只將複製建構函式和賦值運算子宣告為私有的。
我們先來看看第一種方法:將複製建構函式和賦值運算子變成私有的。這種情況看起來類無法複製,但是C++的友元函式可以訪問類的私有屬性和方法,因此也無法避免類被複製。再來看第二種方法:複製建構函式和賦值運算子變成受保護的。和第一種比較像,友元函式也是可以訪問受保護的屬性和方法,而且子類可以訪問受保護的屬性和方法,因此也是無法避免類被複製。而第三種方法:類繼承於一個不可複製的父類,該父類沒有任何屬性,只將複製建構函式和賦值運算子宣告為私有的。在C++中呼叫複製建構函式不僅複製子類部分還會複製父類部分,而複製父類部分則需要父類的複製建構函式是公有的或者受保護的。因此,如果父類的複製建構函式和賦值運算子是私有的,那麼在子類中的任何方法和屬性都無法呼叫父類的複製建構函式和賦值運算子,誤用的情況下,在編譯器在編譯期間就會報錯。我們以實際程式碼作為例子。
#include <iostream>class Uncopyable{protected: Uncopyable() = default; ~Uncopyable() = default;private: Uncopyable(const Uncopyable&){} Uncopyable& operator=(const Uncopyable&){}};template<typename T>class Object : private Uncopyable{public: Object(const T& Value); ~Object() = default;private: T& ObjectValue;};template<typename T>Object<T>::Object(const T& Value):ObjectValue(Value) {}int main(int argc, char *argv[]){ Object<int> NewObject(10); Object<int> OldObject(20); NewObject = OldObject; //錯誤!!!無法呼叫Uncopyable的賦值運演算法 Object<int> TempObject(OldObject); //錯誤!!無法呼叫Uncopyable的複製建構函式 return 0;}
在寫C++程式碼儘量用成員列表初始化的方式初始化成員變數,增加效率和效能。在某些情況下,需要我們建立一個不可複製的類,則將類繼承於複製建構函式和賦值運算子被宣告為私有的父類。