C++11中對類(class)新增的特性
default/delete 控制默認函數
在我們沒有顯式定義類的復制構造函數和賦值操作符的情況下,編譯器會為我們生成默認的這兩個函數:
默認的賦值函數以內存復制的形式完成對象的復制。
這種機制可以為我們節省很多編寫復制構造函數和賦值操作符的時間,但是在某些情況下,比如我們不希望對象被復制,
在之前我們需要將復制構造函數和賦值操作符聲明為private,現在可以使用delete關鍵字實現:
class X {
// …
X& operator=(const X&) = delete; // 禁用類的賦值操作符
X(const X&) = delete;
};
顯式地使用default關鍵字聲明使用類的默認行為,對於編譯器來說明顯是多余的,但是對於代碼的閱讀者來說,使用default顯式地定義復制操作,則意味着這個復制操作就是一個普通的默認的復制操作。
override /final 強制重寫/禁止重寫虛函數
派生類中可以不實現基類虛函數,也可以實現,但不使用virtual關鍵字;
這很容易給人造成混淆,有時為了確認某個函數是否是虛函數,我們不得不追溯到基類查看;
C++11引入了兩個新的標識符: override和final
override,表示函數應當重寫基類中的虛函數。(用於派生類的虛函數中)
final,表示派生類不應當重寫這個虛函數。(用於基類中)
struct B {
virtual void f();
virtual void g() const;
virtual void h(char);
void k(); // non-virtual
virtual void m() final;
};
struct D : B {
void f() override; // OK: 重寫 B::f()
void g() override; // error: 不同的函數聲明,不能重寫
virtual void h(char); // 重寫 B::h( char ); 可能會有警告
void k() override; // error: B::k() 不是虛函數
virtual void m(); // error: m()在基類中聲明禁止重寫
};
有了這對兄弟,我們的虛函數用起來更為安全,也更好閱讀;
委托構造函數 Delegating constructors
在C++98中,如果你想讓兩個構造函數完成相似的事情,可以寫兩個大段代碼相同的構造函數,或者是另外定義一個init()函數,讓兩個構造函數都調用這個init()函數。例如:
class X {
int a;
// 實現一個初始化函數
validate(int x) {
if (0<x && x<=max) a=x; else throw bad_X(x);
}
public:
// 三個構造函數都調用validate(),完成初始化工作
X(int x) { validate(x); }
X() { validate(42); }
X(string s) {
int x = lexical_cast<int>(s); validate(x);
}
// …
};
這樣的實現方式重復羅嗦,並且容易出錯。
在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} { }
// 構造函數X(string s)調用構造函數X(int x)
X(string s) :X{lexical_cast<int>(s)} { }
// …
};
繼承的構造函數 Inheriting constructors
C++11提供了將構造函數晉級的能力:
比如以下這個示例,基類提供一個帶參數的構造函數,而派生類沒有提供;
如果直接使用D1 d(6);將會報錯;通過將基類構造函數晉級,派生類中會隱式聲明構造函數D1(int);
需要注意的是,晉級后的基類構造函數是無法初始化派生類的成員變量的,所以如果派生類中有成員變量,
需要使用初始化列表初始化;
struct B1 {
B1(int) { }
};
struct D1 : B1 {
using B1::B1; // 隱式聲明構造函數D1(int)
// 注意:在聲明的時候x變量已經被初始化
int x{0};
};
void test()
{
D1 d(6); // d.x的值是0
}
類內部成員的初始化 Non-static data member initializers
在C++98標准里,只有static const聲明的整型成員能在類內部初始化,並且初始化值必須是常量表達式。這些限制確保了初始化操作可以在編譯時期進行。
class X {
static const int m1 = 7; // 正確
const int m2 = 7; // 錯誤:無static
static int m3 = 7; // 錯誤:無const
static const string m5 = “odd”; //錯誤:非整型
};
C++11的基本思想是,允許非靜態(non-static)數據成員在其聲明處(在其所屬類內部)進行初始化。這樣,在運行時,需要初始值時構造函數可以使用這個初始值。現在,我們可以這么寫:
class A {
public:
int a = 7;
};
它等同於使用初始化列表:
class A {
public:
int a;
A() : a(7) {}
};
單純從代碼來看,這樣只是省去了一些文字輸入,但在有多個構造函數的類中,其好處就很明顯了:
class A {
public:
A(): a(7), b(5), hash_algorithm(“MD5″),
s(“Constructor run”) {}
A(int a_val) :
a(a_val), b(5), hash_algorithm(“MD5″),
s(“Constructor run”)
{}
A(D d) : a(7), b(g(d)),
hash_algorithm(“MD5″), s(“Constructor run”)
{}
int a, b;
private:
// 哈希加密函數可應用於類A的所有實例
HashingFunction hash_algorithm;
std::string s; // 用以指明對象正處於生命周期內何種狀態的字符串
};
可以簡化為:
class A {
public:
A() {}
A(int a_val) : a(a_val) {}
A(D d) : b(g(d)) {}
int a = 7;
int b = 5;
private:
//哈希加密函數可應用於類A的所有實例
HashingFunction hash_algorithm{“MD5″};
//用以指明對象正處於生命周期內何種狀態的字符串
std::string s{“Constructor run”};
多么優雅!
移動構造和移動賦值
在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 ownership
Handle& operator=(Handle&& h)
{ delete p; p=h.p; h.p=nullptr; } // 傳遞所有權
Handle(const Handle&) = delete; // 禁用拷貝構造函數
Handle& operator=(const Handle&) = delete;
// ...
};
參考
http://www.stroustrup.com/C++11FAQ.html
https://www.chenlq.net/books/cpp11-faq
Posted by: 大CC | 02SEP,2015
博客:blog.me115.com [訂閱]
Github:大CC