Benjamin

静以修身,俭以养德,非澹薄无以明志,非宁静无以致远。
随笔 - 397, 文章 - 0, 评论 - 196, 引用 - 0
数据加载中……

深度理解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"方法。动态绑定就在第二阶段,第二阶段是构造概念的一部分,所以我们可以简单的把从 Base::Base() 移到 Base::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 
Base::Base()
 }
 
 class Derived : public Base {
 public:
   
...
   virtual void foo(int n) const;
   virtual double bar() const;
 };
剩下的就是确定哪里调用第一阶段,哪里调用第二阶段。这里我们要注意以下两点:第一在创建对象时,要加上小小的约束条件,尤其在同一层中有一处或两处要创建
创建时,这个约束可以确保程序不会出错。第二在第一阶段创建对象的方法有三种,是new Derived或是声明
Derived对象,或是不知道具体的类型(通过虚构造或
类工厂创建),用第三种是强壮的,这样可是你很容易的插入一个Derived对象。
在第一阶段,对象的创建一般在堆上,这时我们需要保存一个管理指针(智能指针
std::auto_ptr,引用计数的指针,或是其他的析构删除),最好的预防堆溢出的方法是在第二阶段是抛出异常。分配堆空间简单的示例代码如下:
#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()和
bar(). 如果这两个函数要存取Derived的数据时,这个方法是不能用的。
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
bar(),在Base类构造中加入Helper的引用 
 class Base {
 public:
   Base(const Helper& h);
   ...   
// remove 
   
...   
// remove 
 };
 
 class Derived : public Base {
 public:
   
...   
// remove 
 };
当我们定义Base::Base(const Helper&)时,它会正确调用h.foo(42)和
h.bar()
 Base::Base(const Helper& h)
 {
   ... h.foo(42) ... h.bar() ...

   
// almost identical to the original 
Base::Base()
   
// but with 
h. in calls to h.foo() and h.bar()
 }
Derived::Derived()
   : Base(Helper2())   // the magic happens here

 {
   
...
 }

注意:Derived可以传递值到Helper的派生类的构造中,但不是任何的数据都可以传至Helper派生类。比如Helper::foo()和
Helper::bar() 就不能存取数据在这个类中,特别是数据是Derived类中中声明的数据。

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 
Base::Base()
   
// 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
 {
   
...
 }






posted on 2009-07-04 15:51 Benjamin 阅读(275) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理