多态性和虚函数(Polymorphism&Virtual Functions)

多态性和虚函数(Polymorphism&Virtual Functions)

一、相关日志

多态性与虚函数

http://blog.163.com/zhoumhan_0351/blog/static/3995422720100290234430

二、多态性与虚函数

1、关键点和概念

把函数体与函数调用相联系称为捆绑。晚捆绑只对虚函数起作用。为了达到这个目的,编译器对每个包含虚函数的类创建一个表(VTABLE)。在表中,编译器放置特定的虚函数的地址。在每个带有虚函数的类中,编译器秘密的放置一个指针,称为vpointer(VPTR),指向这个类对象的VTABLE。当通过基类指针做虚函数调用时(多态),编译器静态地插入能取得这个VPTR并在VTABLE表中查找函数地址的代码。这样就能调用正确的函数并引起晚捆绑的发生。

//: C15:Sizes.cpp

// Object sizes with/without virtual functions

#include <iostream>

using namespace std;

class NoVirtual {

  int a;

public:

  void x() const {}

  int i() const { return 1; }

};

class OneVirtual {

  int a;

public:

  virtual void x() const {}

  int i() const { return 1; }

};

class TwoVirtuals {

  int a;

public:

  virtual void x() const {}

  virtual int i() const { return 1; }

};

int main() {

  cout << "int: " << sizeof(int) << endl;

  cout << "NoVirtual: "

   << sizeof(NoVirtual) << endl;

  cout << "void* : " << sizeof(void*) << endl;

  cout << "OneVirtual: "

   << sizeof(OneVirtual) << endl;

  cout << "TwoVirtuals: "

   << sizeof(TwoVirtuals) << endl;

} ///:~

由上面的例子我们可以看出,有虚函数的类的长度是成员变量的总长度加上了一个void指针的长度。它反映出,如果有一个或多个虚函数,编译器都只在这个结构中插入一个单指针VPTR。

每当创建一个包含虚函数的类或从包含虚函数的类派生一个类时,编译器就为这个类创建一个惟一的VTABLE。

对于所有基类对象,或从基类派生类对象,它们的VPTR都在对象的相同位置(常常在对象的开头)。所以编译器可以取出这个对象的VPTR。VPTR指向VTABLE的开始位置。拥有虚函数的同一类簇的所有的VTABLE均有相同的顺序。

注意:不论我们在派生类中是以什么次序重载这些虚函数,它们在VTABLE中的所有函数指针都以相同的次序出现。

在向上类型转换时,只是处理地址。我们还应当意识到,早绑定比晚绑定效率更高。

2、抽象类和纯虚函数

virtual void f() = 0;

告诉编译器在VTABLE中为函数保留一个位置,但在这个特定位置中不放地址。只要有一个函数在类中声明为纯虚函数,VTABLE就是不完全的。纯虚函数禁止对抽象类的函数以传值调用,也是防止对象切片(object slicing)。通过抽象类,可以保证在向上类型转换期间总是使用指针或引用(因为抽象类不能定义对象,所以不能用对象向上类型转换)。

3、纯虚定义

我们可以给纯虚函数进行定义一段公共代码,这样就不需要在每个派生类中都分别定义了(按照如下的形式)。然而,虽然这样定义,纯虚类仍然不能定义对象,且在派生类仍然需要重新定义。

//: C15:PureVirtualDefinitions.cpp

// Pure virtual base definitions

#include <iostream>

using namespace std;

class Pet {

public:

  virtual void speak() const = 0;

  virtual void eat() const = 0;

  // Inline pure virtual definitions illegal:

  //!  virtual void sleep() const = 0 {}

};

// OK, not defined inline

void Pet::eat() const {

  cout << "Pet::eat()" << endl;

}

void Pet::speak() const { 

  cout << "Pet::speak()" << endl;

}

class Dog : public Pet {

public:

  // Use the common Pet code:

  void speak() const { Pet::speak(); }

  void eat() const { Pet::eat(); }

};

int main() {

  Dog simba;  // Richard's dog

  simba.speak();

  simba.eat();

} ///:~

对于可被创建的每个对象(它的类不含有纯虚函数),在它的VTABLE中总有一个函数地址全集。在派生类中没有定义的用基类的地址。

如果在派生类中增加虚函数,而用基类的指针调用,则碰到派生类的虚函数调用时会非法。

//: C15:AddingVirtuals.cpp

// Adding virtuals in derivation

#include <iostream>

#include <string>

using namespace std;

class Pet {

  string pname;

public:

  Pet(const string& petName) : pname(petName) {}

  virtual string name() const { return pname; }

  virtual string speak() const { return ""; }

};

class Dog : public Pet {

  string name;

public:

  Dog(const string& petName) : Pet(petName) {}

  // New virtual function in the Dog class:

  virtual string sit() const {

  return Pet::name() + " sits";

  }

  string speak() const { // Override

return Pet::name() + " says 'Bark!'";

  }

};

int main() {

  Pet* p[] = {new Pet("generic"),new Dog("bob")};

  cout << "p[0]->speak() = "

   << p[0]->speak() << endl;

  cout << "p[1]->speak() = "

   << p[1]->speak() << endl;

 //cout << "p[1]->sit() = "

  //<< p[1]->sit() << endl; // Illegal

} ///:~

当然可以类型转换:

 ((Dog*)p[1])->sit()

4、RTTI

是在关向下类型转换基类指针到派生类指针的问题。向上类型转换是自动发生的,向下类型转换则是不安全的。

当使用对象向上类型转换(不是指针或是引用)时,将发生对象切片,如下图所示,应当比避免使用:

5、对于虚函数,如果我们在派生类中重写,编译器要求我们不能改变基类虚成员函数的返回值(如果不是虚函数是允许的)。但是允许我们改变参数列表的类型和个数,同样,改变了以后基类中成员函数将被隐藏不可调用。但是如果把派生类向上类型转换到基类,则只基类的成员函数可用,而派生类中的方法又将不可行。

//: C15:NameHiding2.cpp

// Virtual functions restrict overloading

#include <iostream>

#include <string>

using namespace std;

class Base {

public:

  virtual int f() const { 

cout << "Base::f()\n"; 

return 1; 

  }

  virtual void f(string) const {}

  virtual void g() const {}

};

class Derived1 : public Base {

public:

  void g() const {}

};

class Derived2 : public Base {

public:

  // Overriding a virtual function:

  int f() const { 

cout << "Derived2::f()\n"; 

return 2;

  }

};

class Derived3 : public Base {

public:

  // Cannot change return type:

  //! void f() const{ cout << "Derived3::f()\n";}

};

class Derived4 : public Base {

public:

  // Change argument list:

  int f(int) const { 

cout << "Derived4::f()\n"; 

return 4; 

  }

};

int main() {

  string s("hello");

  Derived1 d1;

  int x = d1.f();

  d1.f(s);

  Derived2 d2;

  x = d2.f();

//!  d2.f(s); // string version hidden

  Derived4 d4;

  x = d4.f(1);

//!  x = d4.f(); // f() version hidden

//!  d4.f(s); // string version hidden

  Base& br = d4; // Upcast

//!  br.f(1); // Derived version unavailable

  br.f(); // Base version available

  br.f(s); // Base version abailable

} ///:~

6、特例

在上面我们说了,虚成员函数在派生类中不能改变类型,但是如果我们定义虚函数的返回类型是一个指向基类的指针或引用,则在派生类中,我们可以定义该虚函数的返回类型是一个指向其基类的派生类。如下所示:

//: C15:VariantReturn.cpp

// Returning a pointer or reference to a derived

// type during ovverriding

#include <iostream>

#include <string>

using namespace std;

class PetFood {

public:

  virtual string foodType() const = 0;

};

class Pet {

public:

  virtual string type() const = 0;

  virtual PetFood* eats() = 0;

};

class Bird : public Pet {

public:

  string type() const { return "Bird"; }

  class BirdFood : public PetFood {

  public:

string foodType() const { 

  return "Bird food"; 

}

  };

  // Upcast to base type:

  PetFood* eats() { 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"; }

  };

  // Return exact type instead:

  CatFood* eats() { return &cf; }

private:

  CatFood cf;

};

int main() {

  Bird b; 

  Cat c;

  Pet* p[] = { &b, &c, };

  for(int i = 0; i < sizeof p / sizeof *p; i++)

cout << p[i]->type() << " eats "

 << p[i]->eats()->foodType() << endl;

  // Can return the exact type:

  Cat::CatFood* cf = c.eats();

  Bird::BirdFood* bf;

  // Cannot return the exact type:

//!  bf = b.eats();

  // Must downcast:

  bf = dynamic_cast<Bird::BirdFood*>(b.eats());

} ///:~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值