天天看點

多态性和虛函數(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。

多态性和虛函數(Polymorphism&amp;Virtual Functions)
多态性和虛函數(Polymorphism&amp;Virtual Functions)

每當建立一個包含虛函數的類或從包含虛函數的類派生一個類時,編譯器就為這個類建立一個惟一的VTABLE。

多态性和虛函數(Polymorphism&amp;Virtual Functions)

對于所有基類對象,或從基類派生類對象,它們的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

} ///:~

多态性和虛函數(Polymorphism&amp;Virtual Functions)

當然可以類型轉換:

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

4、RTTI

是在關向下類型轉換基類指針到派生類指針的問題。向上類型轉換是自動發生的,向下類型轉換則是不安全的。

當使用對象向上類型轉換(不是指針或是引用)時,将發生對象切片,如下圖所示,應當比避免使用:

多态性和虛函數(Polymorphism&amp;Virtual Functions)

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());

} ///:~

繼續閱讀