C++11构造与禁用默认函数
C++11(五)繼承構造
在繼承體系中,假設派生類想要使用基類的構造函數,必須要在構造函數中顯式聲明。
舉個小例子:
class Base { public:int value1;int value2;Base() {value1 = 1;}Base(int value) : Base() { // 委托 Base() 構造函數value2 = value;} }; class Subclass : public Base { public:using Base::Base; // 繼承構造 };那么它解決了什么問題呢?
下面參考了:https://www.runoob.com/w3cnote/cpp11-inheritance-constructor.htm
看看下面的小問題:
struct?A{ A(int i){}};struct?B:A{ B(int i):A(i){}};在這里,B派生于A,B又在構造函數中調用A的構造函數。從而完畢構造函數的傳遞。
又比方例如以下。當B中存在成員變量時:
struct?A{A(int i){} }; struct?B:A{B(int i):A(i),d(i){}int d; };如今派生于A的結構體B包括一個成員變量,我們在初始化基類A的同一時候也初始化成員d。如今的問題是:假若基類用于擁有為數眾多的不同版本號的構造函數。這樣,在派生類中按上面的思維還得寫非常多相應的"透傳"構造函數。例如以下:
struct?A{A(int i) {}A(double d,int i){}A(float f,int i,const char* c){}//...等等系列的構造函數版本號 }; struct?B:A{B(int i):A(i){}B(double d,int i):A(d,i){}B(folat f,int i,const char* c):A(f,i,e){}//......等等好多個和基類構造函數相應的構造函數 };非常明顯當基類構造函數一多,派生類構造函數的寫法就顯得非常累贅,相當不方便。
?
問題的解決
我們能夠通過using聲明來完畢這個問題的簡化,看一個樣例
struct?Base{void f(double i){cout<<"Base:"<<i<<endl;} }; struct?Drived:Base{using Base::f;void f(int i){cout<<"Drived:"<<i<<endl;} };代碼中基類和派生類都聲明了同名的函數f。但派生類中辦法和基類的版本號不同,這里使用using聲明,說明派生類中也使用基類版本號的函數f。這樣派生類中就擁有兩個f函數的版本號了。在這里必須要說明的是,假設沒有使用using聲明繼承父類同名函數,那么派生類中定義的f函數將會屏蔽父類的f函數,當然若派生類根本就未定義這個f同名函數。還會選擇用基類的f函數。
這樣的方法,我們一樣可遷移到構造函數的繼承上。即派生類能夠通過using語句聲明要在子類中繼承基類的全部構造函數。例如以下:
struct A {A(int i) {}A(double d,int i){}A(float f,int i,const char* c){}//...等等系列的構造函數版本號 }; struct B:A {using A::A;//關于基類各構造函數的繼承一句話搞定//...... };如今,通過using A::A的聲明。將基類中的構造函數全繼承到派生類中,更巧妙的是,這是隱式聲明繼承的。即假設一個繼承構造函數不被相關的代碼使用,編譯器不會為之產生真正的函數代碼,這樣比透傳基類各種構造函數更加節省目標代碼空間。但此時另一個問題:
當使用using語句繼承基類構造函數時。派生類無法對類自身定義的新的類成員進行初始化,我們可使用類成員的初始化表達式,為派生類成員設定一個默認初始值。比方:
struct A {A(int i) {}A(double d,int i){}A(float f,int i,const char* c){}//...等等系列的構造函數版本號 }; struct B:A {using A::A;int d{0}; };注意:
1.對于繼承構造函數來說,參數的默認值是不會被繼承的,并且,默認值會 導致基類產生多個構造函數版本號(即參數從后一直往前面減。直到包括無參構造函數,當然假設是默認復制構造函數也包括在內),這些函數版本號都會被派生類繼承。
2.繼承構造函數中的沖突處理:當派生類擁有多個基類時,多個基類中的部分構造函數可能導致派生類中的繼承構造函數的函數參數都同樣,那么繼承類中的繼承構造函數將導致不合法的派生類代碼,比方:
struct?A{A(int){} }; struct?B{B(int){} }; struct?C:A,B{using A::A;using B::B; };在這里將導致派生類中的繼承構造函數發生沖突,一個解決的辦法就是顯式指定繼承類的沖突構造函數。阻止隱式生成對應的繼承構造函數,以免發生沖突。
struct?C:A,B{using A::A;using B::B;C(int){} };3.假設基類的構造函數被聲明為私有構造函數或者派生類是從基類虛繼承的,那么就不能在派生類中聲明繼承構造函數。
4.假設一旦使用了繼承構造函數,編譯器就不會為派生類生成默認構造函數。這樣,我們得注意繼承構造函數無參版本號是不是有必要。
?
C++11(六) 委托構造
《C++Primer》一書中關于委托構造函數是這樣描述的:
一個委托構造函數使用它所屬的類的其他構造函數執行自己的初始化過程,或者說它把自己的一些(或者全部)職責委托給了其他構造函數。和其他構造函數一樣,一個委托構造函數也有一個成員初始值的列表和一個函數體。在委托構造函數內,成員的初始值列表只有一個唯一的入口,就是類名本身。和其他成員初始值一樣,類名后面緊跟圓括號括起來的參數列表,參數列表必須與類中另外一個構造函數匹配。
?
概念比較難懂,下面舉個小例子:
class Base {public:int value1;int value2;Base() {value1 = 1;}Base(int value) : Base() { // 委托Base() 構造函數value2 = value;} };why?
在C++98中,如果你想讓兩個構造函數完成相似的事情,可以寫兩個大段代碼相同的構造函數,或者是另外定義一個init()函數,讓兩個構造函數都調用這個init()函數。例如:
class X {int a;// 實現一個初始化函數init(int x) {if (0<x && x<=max) a=x; else throw bad_X(x);}public:// 三個構造函數都調用validate(),完成初始化工作X(int x) { init(x); }X()?{?init(42);?} };這樣的實現方式重復羅嗦,并且容易出錯。并且,這兩種方式的可維護性都很差。所以,在C++0x中,我們可以在定義一個構造函數時調用另外一個構造函數:
class X {int a;public:X(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }// 構造函數X()調用構造函數X(int x)X()?:X{42}?{?} };一個構造函數想要委托另一個構造函數,那么被委托的構造函數應該包含較大數量的參數,初始化較多的成員變量。而且在委托其他構造函數后,不能再進行成員列表初始化,而只能在函數體內進行初始化其他成員變量。
?
C++11(七)禁用默認函數
在傳統C++中,若用戶沒有提供, 則編譯器會自動為對象生成默認構造函數(default constructor)、 復制構造函數(copy constructor),賦值運算符(copy assignment operator operator=) 以及析構式(destructor)。另外,C++也為所有的類定義了數個全局運算符(如operator delete及operator new)。當用戶有需要時,也可以提供自定義的版本改寫上述的函數。
問題在于原先的c++無法精確地控制這些默認函數的生成。比方說,要讓類型不能被拷貝,必須將復制構造函數與賦值運算符聲明為private,并不去定義它們。嘗試使用這些未定義的函數會導致編譯期或鏈接期的錯誤。但這種手法并不是一個理想的解決方案。
此外,編譯器產生的默認構造函數與用戶定義的構造函數無法同時存在。若用戶定義了任何構造函數,編譯器便不會生成默認構造函數;但有時同時帶有上述兩者提供的構造函數也是很有用的。目前并沒有顯式指定編譯器產生默認構造函數的方法。
C++11 允許顯式地表明采用或拒用編譯器提供的自帶函數。例如要求類型帶有默認構造函數,可以用以下的語法:
class Magic {public:Magic() = default; // 顯式聲明使用編譯器生成的構造Magic &operator=(const Magic &) = delete; // 顯式聲明拒絕編譯器生成構造Magic(int magic_number); }顯式地使用default關鍵字聲明使用類的默認行為,對于編譯器來說明顯是多余的,但是對于代碼的閱讀者來說,使用default顯式地定義復制操作,則意味著這個復制操作就是一個普通的默認的復制操作。將默認的操作留給編譯器去實現將更加簡單,更少的錯誤發生 ,并且通常會產生更好的目標代碼。
“default”關鍵字可以用在任何的默認函數中,而“delete”則可以用于修飾任何函數。例如,我們可以通過下面的方式排除一個不想要的函數參數類型轉換:
移動構造和移動賦值
在C++98中,我們自定義的類,會默認生成拷貝賦值操作符函數和拷貝賦值函數以及析構函數;
在C++11中,依賴于新增的move語義,默認生成的函數多了2個移動相關的:移動賦值操作符( move assignment )和移動構造函數( move constructor );
BS建議,如果你顯式聲明了上述 5 個函數或操作符中的任何一個,你必須考慮其余的 4 個,并且顯式地定義你需要的操作,或者使用這個操作的默認行為。
一旦我們顯式地指明( 聲明 , 定義 , =default , 或者 =delete )了上述五個函數之中的任意一個,編譯器將不會默認自動生成move操作。
一旦我們顯式地指明( 聲明 , 定義 , =default , 或者 =delete )了上述五個函數之中的任意一個,編譯器將默認自動生成所有的拷貝操作。但是,我們應該盡量避免這種情況的發生,不要依賴于編譯器的默認動作。
如果你聲明了上述 5 個默認函數中的任何一個,強烈建議你顯式地聲明所有這 5 個默認函數。例如:
template<class T> class Handle {T* p; public:Handle(T* pp) : p{pp} {}// 用戶定義構造函數: 沒有隱式的拷貝和移動操作~Handle() { delete p; }Handle(Handle&& h) :p{h.p}{ h.p=nullptr; }; // transfer ownershipHandle& operator=(Handle&& h){ delete p; p=h.p; h.p=nullptr; } // 傳遞所有權Handle(const Handle&) = delete; // 禁用拷貝構造函數Handle& operator=(const Handle&) = delete;// ... };總結
以上是生活随笔為你收集整理的C++11构造与禁用默认函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++11强类型枚举,override/
- 下一篇: 命令模式(C++)