深度理解C++概念之繼承(二)
1.有關 private virtuals:幾乎不用。如果沒有特殊的原因,不提倡使用。
2.當基類構造函數調用虛函數時,為什么不調用派生類重寫的該虛函數?這樣做是危險的,C++會阻止你這樣做
coder:
#include <iostream>
#include <string>
void println(const std::string& msg)
{ std::cout << msg << '\n'; }
class Base {
public:
Base() { println("Base::Base()"); virt(); }
virtual void virt() { println("Base::virt()"); }
};
class Derived : public Base {
public:
Derived() { println("Derived::Derived()"); virt(); }
virtual void virt() { println("Derived::virt()"); }
};
int main()
{
Derived d;
...
}
程序輸出:
Base::Base()
Base::virt() // ← Not Derived::virt()
Derived::Derived()
Derived::virt()
當基類被構造時,對象還不是一個派生類的對象,所以如果 Base::Base()調用了虛函數 virt(),則 Base::virt() 將被調用,即使 Derived::virt()(即派生類重寫的虛函數)存在。
同樣,當基類被析構時,對象已經不再是一個派生類對象了,所以如果 Base::~Base()調用了virt(),則 Base::virt()得到控制權,而不是重寫的 Derived::virt() 。
如果 Base::Base()調用了虛函數 virt(),這個規則使得 Base::virt()被調用。如果不按照這個規則,Derived::virt()將在派生對象的派生部分被構造之前被調用,此時屬于派生對象的派生部分的某個成員對象還沒有被構造,而 Derived::virt()卻能夠訪問它。這將是災難。
3.模擬動態綁定在一個基類的構造里的方法
class Base {
public:
Base();
...
virtual void foo(int n) const; // often pure virtual
virtual double bar() const; // often pure virtual
// if you don't want outsiders calling these, make them protected
};
Base::Base()
{
... foo(42) ... bar() ...
// these will not use dynamic binding
// goal: simulate dynamic binding in those calls
}
class Derived : public Base {
public:
...
virtual void foo(int n) const;
virtual double bar() const;
};
實現方法有兩種,根據自己實際情況來選擇。
第一種方法,把初始化分兩個階段,第一階段是實際的構造,第二階段是"init"方法。動態綁定就在第二階段,第二階段是構造概念的一部分,所以我們可以簡單的把從
class Base {
public:
void init(); // may or may not be virtual
...
virtual void foo(int n) const; // often pure virtual
virtual double bar() const; // often pure virtual
};
void Base::init()
{
... foo(42) ... bar() ...
// most of this is copied from the original
}
class Derived : public Base {
public:
...
virtual void foo(int n) const;
virtual double bar() const;
};
剩下的就是確定哪里調用第一階段,哪里調用第二階段。這里我們要注意以下兩點:第一在創建對象時,要加上小小的約束條件,尤其在同一層中有一處或兩處要創建
創建時,這個約束可以確保程序不會出錯。第二在第一階段創建對象的方法有三種,是new Derived或是聲明Derived對象,或是不知道具體的類型(通過虛構造或
類工廠創建),用第三種是強壯的,這樣可是你很容易的插入一個Derived對象。
在第一階段,對象的創建一般在堆上,這時我們需要保存一個管理指針(智能指針
#include <memory>
void joe_user()
{
std::auto_ptr<Base> p(/*...somehow create a Derived object via new...*/);
p->init();
...
}
第二種方案是組合joe_user前兩句到create函數里。如果你是用工廠(factory)模式,譬如虛構造,你可以這兩行放在靜態方法中調用Base::create(): 代碼如下
#include <memory>
class Base {
public:
...
typedef std::auto_ptr<Base> Ptr; // typedefs simplify the code
static Ptr create();
...
};
Base::Ptr Base::create()
{
Ptr p(/*...use a factory to create a Derived object via new...*/);
p->init();
return p;
}
它簡化了joe_user的功能,更重要的是可以在創建對象不用調用Init().
void joe_user()
{
Base::Ptr p = Base::create();
...
}
我們在這個方法中,我們應當竭力避免調用Init(),那么就應該使派生類的構造、拷貝構造成為priviate或protected;
最后的方法,則和上面的都不同,在第二層類結構中加入foo()和
class Helper {
public:
virtual void foo(int n) const = 0;
virtual double bar() const = 0;
};
class Helper1 : public Helper {
public:
virtual void foo(int n) const;
virtual double bar() const;
};
class Helper2 : public Helper {
public:
virtual void foo(int n) const;
virtual double bar() const;
};
Base類也要刪除init(),Base類和Derived類要刪除foo() and
class Base {
public:
Base(const Helper& h);
...
...
};
class Derived : public Base {
public:
...
};
當我們定義Base::Base(const Helper&)時,它會正確調用h.foo(42)和
{
... h.foo(42) ... h.bar() ...
// almost identical to the original
// but with
}
Derived::Derived()
: Base(Helper2()) // ←the magic happens here
{
...
}
注意:Derived可以傳遞值到Helper的派生類的構造中,但不是任何的數據都可以傳至Helper派生類。比如Helper::foo()和
Helper派生類也可以做成類似joe_user功能,例如:
Derived::Derived(const Helper& h)
: Base(h)
{
...
}
如果Helper不需要數據,那么可以通過一個靜態方法來替代它。
class Base {
public:
typedef void (*FooFn)(int); // typedefs simplify
typedef double (*BarFn)(); // the rest of the code
Base(FooFn foo, BarFn bar);
...
};
Base::Base(FooFn foo, BarFn bar)
{
... foo(42) ... bar() ...
// almost identical to the original
// except calls are made via function pointers.
}
class Derived : public Base {
public:
Derived();
static void foo(int n); // the static is important!
static double bar(); // the static is important!
...
};
Derived::Derived()
: Base(foo, bar) // ←pass the function-ptrs into Base's ctor
{
...
}