随笔 - 55  文章 - 15  trackbacks - 0
<2012年5月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用链接

留言簿

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

一个函数接受一个基类的指针或者引用,传入一个子类的指针或者引用(向上类型转换),希望调用子类的相应函数。目的:以后添加新的子类,都可以传入该函数。
早绑定:编译器通过上下文,判断该函数属于哪个对象,并在编译期将函数名与函数地址绑定。
晚绑定:在运行的时候,判断该函数属于哪个对象,并在运行时将函数名与函数地址绑定。必须有类型信息装在对象自身中。
声明时添加virtual关键字,定义时不需要。

使用指针和引用的目的是让编译器不能完全知道该对象的确切类型,不然就会调用早绑定。晚绑定是根据VTABLE来实现,并且基类和子类的每个虚函数的排列顺序都是相同的,所以调用函数的时候已经不是通过名字来调用,而是通过指令,通过函数地址的偏移量来调用了。

抽象基类的意义,为子类提供一个公共的接口。

通过基类指针调用基类中不存在的函数是危险的,因为,也许你恰好知道子类对象中有这个函数,那你的调用时成功的,但是万一木有呢?
class Base(){
public:
 virtual void f(){}
};

class Derived1: public Base
{
public:
 virtual void f(){}
 virtual void g(){}
};
class Derived2: public Base
{
public:
 virtual void f(){}
 virtual void m(){}
};

void func(Base* b){
b->g();
}
int main{
Base*Test1 = new Derived1;
Base*Test2 = new Derived2;
func(Test1);// right;
func(Test2);// crash
}

这里涉及到运行时类型识别(RTTI)和向下类型转换问题。向下类型转换不安全,因为没有类型信息,基类指针不知道基类的内存块之后的东西是属于哪个子类的,如果转错,将会比较麻烦。

在编程时注意防止对象的切片,如果按传值方式而不是传址和传引用方式
将子类对象传入一个接受基类对象的函数中去的话,那么,只拷贝子类对象中基类的部分数据,又因为编译器能明确地知道该对象的类型,所以不会产生晚绑定,而是早绑定。我们应该避免在这种情况下传值。

如果重新定义了基类中的虚函数,则基类中其他重载版本将被隐藏。(同非虚函数一样)
如果重载了基类中的虚函数,则基类中其他版本将被隐藏(同非虚函数一样)
不能在子类中修改基类中虚函数的返回值(非虚函数可以修改返回值,并且隐藏其他重载版本)
但是,也有特例
class PetFood{
public:
  virtual string foodType() const = 0;
};

class Pet{
public:
  virtual string type() const = 0;
  virtual PetFood* eat() = 0;
};

class Bird : public Pet{
public:
  string type()const {return "bird";}
  class BirdFood  :  public PetFood{
    public:
      string foodType()const{
         return "Bird food";
       }
     };
   PetFood* eat(){ return &bf;}
private:
   BirdFood bf;
};

class Cat: public Pet{
public:
  string type()const {return "cat";}
  class CatFood  :  public PetFood{
    public:
      string foodType()const{
         return "Cat food";
       }
     };
   CatFood* eat(){ return &cf;}// Here, you can return a CatFood*, because it's a PetType* type. Why don't return a type as PetFood? See segment in main()
private:
   CatFood cf;
};

int main(){
 Bird b;
 Cat c;
 Cat::CatFood* cf = c.eat();
 Bird::BirdFood* bf = b.eat();//downcast, warning!!!Cast PetFood* to BirdFood. So you better return a special pointer, not a base type.
}
}
返回确切的类型要更通用些。

vptr vtable由谁来初始化?构造函数?是编译器插入一小段代码在构造函数中初始化。
派生类只访问它自己的成员,而不访问基类的成员。只有基类的构造函数才能正确地初始化自己的成员。所以要在构造函数中:子要可能,我们应该在这个构造函数初始化列表中从初始化所有的成员对象(通过组合置于类中),因为你必须保证所有的东西都被初始化了,才能使用该对象。

在构造函数中调用虚函数,调用的只是本地版本。
原因:该对象还未初始完毕,但是vptr已经初始化,而且指向自己的vtable,所以调用的只是本地的函数。


1
posted on 2012-06-04 16:27 Dino-Tech 阅读(235) 评论(0)  编辑 收藏 引用

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