目录
二、虚函数的实现机制——虚函数表(virtual table)
一、什么是虚函数(virtual function)
虚函数用来实现C++的多态性(polymorphism)。
当基类的指针指向派生类实例时,可以用基类指针调用派生类中的成员函数。如果基类指针指向不同的派生类,则该指针可以调用同一个函数实现不同的逻辑。这种机制可以让基类指针具有多种形态。示例如下:
/*********************************
* 虚函数
*/
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
};
class Derive : public Base {
public:
virtual void funcA() { std::cout << "Derive::funcA" << std::endl; }
};
class Derive2 : public Base {
public:
virtual void funcA() { std::cout << "Derive2::funcA" << std::endl; }
};
int main(int argc, char** argv) {
Derive d;
Base* bptr = &d;
bptr->funcA();
Derive2 d2;
bptr = &d2;
bptr->funcA();
system("pause");
return 0;
}
运行结果:
二、虚函数的实现机制——虚函数表(virtual table)
2.1 虚函数表
虚函数机制是通过虚函数表实现的。虚函数表是指每个包含虚函数的类中,都存在的一个函数地址数组。虚函数表位于对象实例的最前面,后面是成员变量。即:
代码示例:
/*********************************
* 虚函数表
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
Base() :
m_item1(0),
m_item2(0) {
}
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
int m_item1;
int m_item2;
};
int main(int argc, char** argv) {
std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
Base base;
// 对象base的地址
int* bAddress = (int*)&base;
std::cout << "base address: 0x" << std::setw(8) << bAddress << std::endl;
// 对象base虚函数表的地址
int* vtptr = (int*)*(bAddress + 0);
std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
// funcA的地址
int* pfuncA = (int *)*(vtptr + 0);
std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
// funcB的地址
int* pfuncB = (int *)*(vtptr + 1);
std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
// funcC的地址
int* pfuncC = (int *)*(vtptr + 2);
std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
system("pause");
return 0;
}
在visual studio中可以看到输出局部变量的情况:
控制台输出为:
2.2 单继承下的虚函数表
2.2.1 无覆盖
新定义子类Derived,继承自Base,不覆盖Base中的虚函数,如下
示例代码如下:
/*********************************
* 虚函数表,单继承,无覆盖
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
Base() :
m_item1(0),
m_item2(0) {
}
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
int m_item1;
int m_item2;
};
class Derive : public Base {
public:
Derive() : m_item3(0) {}
virtual void funcD() { std::cout << "Derive::funcD" << std::endl; }
private:
int m_item3;
};
int main(int argc, char** argv) {
std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
Derive d;
// 对象d的地址
int* dAddress = (int*)&d;
std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
// 对象d虚函数表的地址
int* vtptr = (int*)*(dAddress + 0);
std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
// funcA的地址
int* pfuncA = (int *)*(vtptr + 0);
std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
// funcB的地址
int* pfuncB = (int *)*(vtptr + 1);
std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
// funcC的地址
int* pfuncC = (int *)*(vtptr + 2);
std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
// funcD的地址
int* pfuncD = (int *)*(vtptr + 3);
std::cout << "pfuncD address: 0x" << std::setw(8) << pfuncD << std::endl;
system("pause");
return 0;
}
2.2.2 有覆盖
代码如下:
/*********************************
* 虚函数表-单继承有覆盖
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
Base() :
m_item1(0),
m_item2(0) {
}
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
int m_item1;
int m_item2;
};
class Derive : public Base {
public:
Derive() : m_item3(0) {}
virtual void funcA() { std::cout << "Derive::funcA" << std::endl; }
private:
int m_item3;
};
int main(int argc, char** argv) {
std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
Derive d;
// 对象d的地址
int* dAddress = (int*)&d;
std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
// 对象d虚函数表的地址
int* vtptr = (int*)*(dAddress + 0);
std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
// funcA的地址
int* pfuncA = (int *)*(vtptr + 0);
std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
// funcB的地址
int* pfuncB = (int *)*(vtptr + 1);
std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
// funcC的地址
int* pfuncC = (int *)*(vtptr + 2);
std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
system("pause");
return 0;
}
运行结果如下:
可以看到d中基类的虚函数funcA已经被继承类覆盖。这种现象叫做重写(override)基类中的funcA函数被隐藏了。
这时,如果想要继续调用积累的funcA改如何实现呢?
d.Base::funcA();
2.3 多继承下的虚函数表
2.3.1 无覆盖
派生类Derive同时继承基类 Base,Base2,则继承类中有两个虚函数表,如下:
代码如下:
/*********************************
* 虚函数表
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
Base() :
m_item1(0),
m_item2(0) {
}
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
int m_item1;
int m_item2;
};
class Base2 {
virtual void funcD() { std::cout << "Base2::funcD" << std::endl; }
};
class Child :public Base, public Base2 {
};
int main(int argc, char** argv) {
std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
Child d;
// 对象d的地址
int* dAddress = (int*)&d;
std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
// 对象d虚函数表的地址
int* vtptr = (int*)*(dAddress + 0);
std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
// funcA的地址
int* pfuncA = (int *)*(vtptr + 0);
std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
// funcB的地址
int* pfuncB = (int *)*(vtptr + 1);
std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
// funcC的地址
int* pfuncC = (int *)*(vtptr + 2);
std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
system("pause");
return 0;
}
2.3.2 有覆盖
代码:
/*********************************
* 虚函数表
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
Base() :
m_item1(0),
m_item2(0) {
}
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
int m_item1;
int m_item2;
};
class Base2 {
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
virtual void funcD() { std::cout << "Base2::funcD" << std::endl; }
};
class Child :public Base, public Base2 {
virtual void funcD() { std::cout << "Child::funcD" << std::endl; }
};
int main(int argc, char** argv) {
std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
Child d;
// 对象d的地址
int* dAddress = (int*)&d;
std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
// 对象d虚函数表的地址
int* vtptr = (int*)*(dAddress + 0);
std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
// funcA的地址
int* pfuncA = (int *)*(vtptr + 0);
std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
// funcB的地址
int* pfuncB = (int *)*(vtptr + 1);
std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
// funcC的地址
int* pfuncC = (int *)*(vtptr + 2);
std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
system("pause");
return 0;
}
基类Base和基类Base2中都有的函数,应该如何调用?
/*********************************
* 虚函数表
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
Base() :
m_item1(0),
m_item2(0) {
}
virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
int m_item1;
int m_item2;
};
class Base2 {
public:
virtual void funcA() { std::cout << "Base2::funcA" << std::endl; }
virtual void funcD() { std::cout << "Base2::funcD" << std::endl; }
};
class Child :public Base, public Base2 {
public:
virtual void funcA() { std::cout << "Child::funcA" << std::endl; }
virtual void funcD() { std::cout << "Child::funcD" << std::endl; }
};
class Derive : public Base {
public:
Derive() : m_item3(0) {}
virtual void funcA() { std::cout << "Derive::funcA" << std::endl; }
private:
int m_item3;
};
int main(int argc, char** argv) {
std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
Child d;
// 对象d的地址
int* dAddress = (int*)&d;
std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
// 对象d虚函数表的地址
int* vtptr = (int*)*(dAddress + 0);
std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
// funcA的地址
int* pfuncA = (int *)*(vtptr + 0);
std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
// funcB的地址
int* pfuncB = (int *)*(vtptr + 1);
std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
// funcC的地址
int* pfuncC = (int *)*(vtptr + 2);
std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
d.funcA();
d.Base::funcA();
d.Base2::funcA();
Base* ptr = (Base*)&d;
ptr->funcA();
system("pause");
return 0;
}
2.4 构造函数不能是虚函数,析构函数可以是虚函数
2.4.1 构造函数不能是虚函数的原因
从内存角度:构造函数的作用是用于类对象的初始化。而虚函数的动态绑定是通过虚函数表实现的。调用构造函数之前系统只是对类对象分配了内存,并没有对内存进行初始化,此时内存空间上不存在虚函数表,也就无法通过虚函数表调用到虚函数。
从必要性角度:虚函数的意义是动态绑定。但是当定义派生类对象时,是依次调用构造函数,基类的构造函数——> 一级派生类的构造函数——> ……——>派生类的构造函数。不需要动态绑定。
2.4.2 析构函数可以是虚函数
基类指针或者引用,可以实现对虚函数的动态绑定。当我们用积累指针或者引用析构一个对象时,无法判断当前指针或者引用指向的是基类还是派生类,如果析构函数不是虚函数,就会直接调用基类的析构函数,这时候就会发生问题。因此需要析构函数是虚函数,才能通过动态绑定,调用到应该调用的析构函数。