三 2 Year 2011 陳 良喬 C++11 FAQ
繼承的構造函數
人們有時會對類成員函數或成員變量的作用域問題感到困惑,尤其是,當基類與派生類的同名成員不在同一個作用域内時:
struct B {
void f(double);
};
struct D : B {
void f(int);
};
B b; b.f(4.5); // 可行
// 調用的到底是B::f(doube)還是D::f(int)呢?
// 實際情況往往會讓冉感到意外:調用的f(int)函數實參為4
D d; d.f(4.5);
在C++98标準裡,可以将普通的重載函數從基類“晉級”到派生類裡來解決這個問題:
struct B {
void f(double);
};
struct D : B {
using B::f; // 将類B中的f()函數引入到類D的作用域内
void f(int); // 增加一個新的f()函數
};
B b; b.f(4.5); // 可行
// 可行:調用類D中的f(double)函數
// 也即類B中的f(double)函數
D d; d.f(4.5);
普通重載函數可以通過這種方式解決,那麼,對于構造函數又該怎麼辦呢? 我已經說過“不能像應用于普通成員函數那樣,将上述文法應用于構造函數,這如曆史偶然一樣”。為了解決構造函數的“晉級”問題,C++11提供了這種能力:
class Derived : public Base {
public:
// 提升Base類的f函數到Derived類的作用範圍内
// 這一特性已存在于C++98标準内
using Base::f;
void f(char); // 提供一個新的f函數
void f(int); // 與Base類的f(int)函數相比更常用到這個f函數
// 提升Base類的構造函數到Derived的作用範圍内
// 這一特性隻存在于C++11标準内
using Base::Base;
Derived(char); // 提供一個新的構造函數
// 與Base類的構造函數Base(int)相比
// 更常用到這個構造函數
Derived(int);
// …
};
如果這樣用了,仍然可能困惑于派生類中繼承的構造函數,這個派生類中定義的新成員變量需要初始化(譯注:基類并不知道派生類的新增成員變量,當然不會對其進行初始化。):
struct B1 {
B1(int) { }
};
struct D1 : B1 {
using B1::B1; // 隐式聲明構造函數D1(int)
int x;
};
void test()
{
D1 d(6); // 糟糕:調用的是基類的構造函數,d.x沒有初始化
D1 e; // 錯誤:類D1沒有預設的構造函數
}
我們可以通過使用成員初始化(member-initializer)消除以上的困惑:
struct D1 : B1 {
using B1::B1; // 隐式聲明構造函數D1(int)
// 注意:x變量已經被初始化
// (譯注:在聲明的時候就提供初始化)
int x{0};
};
void test()
{
D1 d(6); // d.x的值是0
}
或參見:
- the C++ draft 8.5.4 List-initialization [dcl.init.list]
-
[N1890=05-0150 ] Bjarne Stroustrup and Gabriel Dos Reis:
Initialization and initializers
(an overview of initialization-related problems with suggested solutions).
-
[N1919=05-0179] Bjarne Stroustrup and Gabriel Dos Reis:
Initializer lists.
-
[N2215=07-0075] Bjarne Stroustrup and Gabriel Dos Reis :
Initializer lists (Rev. 3) .
-
[N2640=08-0150] Jason Merrill and Daveed Vandevoorde:
Initializer Lists — Alternative Mechanism and Rationale (v. 2) (final proposal).