C++ 虚函数的内部实现
2021-06-06 12:03
标签:面向对象 结果 相关 分布 关键字 try 赋值 int() 绑定 虚函数看起来是个玄之又玄的东西,但其实特别简单!了解了虚函数的内部实现,关于虚函数的各种问题都不在话下啦! 阅读这篇文章,你需要事先了解以下几个概念: 什么是继承? 什么是虚函数? 在C++中,在基类的成员函数声明前加上关键字 为什么需要虚函数? 这涉及到面向对象程序设计中多态、动态绑定的概念。 如果你已经完全了解上述概念,那么这篇文章很适合你去深入了解虚函数~ 为了更好地理解虚函数的内部实现,我们首先需要知道,C++的类中成员函数和成员变量在内存中的空间分配。 如果我们在这个程序中定义了这个类的一个对象,那么这个类的内存分布如下图所示: 类的非静态成员变量会被保存在栈上,类的静态成员变量被保存在数据段,而类的成员函数被保存在代码段。 如果一个类中含有虚函数,那么为了实现动态绑定,编译器会在原来的代码中插入(augment)一个新的成员变量--一个成员指针 一般而言,这张 现在来看一个例子~ 此时,基类的成员变量和成员函数相当于派生类的子对象,也就是说派生类会继承基类的 如果派生类写了一个不在基类里的新的虚函数,那么这个 有多个基类的派生类会有多个 我们来看一个Z类继承自X类和Y类的例子。 了解了虚函数在内存中的分配方式后,理解虚函数的实现以及动态绑定就变得非常简单了。 这里以多继承的子类的代码为例,上代码~ 所以在上述代码中, 注意?? 此时 而 其中 由于这个 沿用3中的例子,我们来看接下来的几个问题。 即如下代码中,输出是 答案是不会发生,输出的结果是 首先我们需要明确一个很重要的概念,对 不论是赋值操作还是赋值构造时, 只会处理成员变量,即把zz中的成员变量赋值给yy, 但是 即如下代码中,输出是 答案是不会发生,输出的结果是 以上为个人学习总结,如有错误欢迎指出! 这篇博文参考了如下文章: http://www.vishalchovatiya.com/memory-layout-of-cpp-object/ http://www.vishalchovatiya.com/part-1-all-about-virtual-keyword-in-cpp-how-virtual-function-works-internally/ https://www.learncpp.com/cpp-tutorial/the-virtual-table/ https://www.cnblogs.com/yinheyi/p/10525543.html C++ 虚函数的内部实现 标签:面向对象 结果 相关 分布 关键字 try 赋值 int() 绑定 原文地址:https://www.cnblogs.com/zxyLeaf/p/virtual_func_cpp.htmlC++ 虚函数的内部实现
1. 知识储备
virtual
即可让该函数成为 虚函数,派生类中对此函数的不同实现都会继承这一修饰符。
2. C++中类的memory Layout
1. 我们从最普通的一个类说起~
class X {
int x;
float xx;
static int count;
public:
X() {}
~X() {}
void printInt() {}
void printFloat() {}
static void printCount() {}
};
class X {
int x;
float xx;
static int count;
public:
X() {}
~X() {}
void printInt() {}
void printFloat() {}
static void printCount() {}
};
2. 含有虚函数的基类的内存分布
vptr
, 这个指针指向一张包含所有虚函数的函数指针表 vtable
. 当我们调用虚函数时,实际上是通过vptr
这个指针来调用函数指针表vtable
里面的某个函数指针来实现函数调用。vtable
会在数据段,是静态的,每一个类仅有一张表。但是这不是死规定,这是由编译器的实现决定的。vptr
这个指针和成员变量一致,存在在堆栈段,是每一个对象都会有的。vtable
中的第一个entry包含了当前类及其基类的相关信息,其余entry是函数指针。
class X {
int x;
float xx;
static int count;
public:
X() {}
virtual ~X() {}
virtual void printAll() {}
void printInt() {}
void printFloat() {}
static void printCount() {}
};
3. 含有虚函数的子类的内存分布
vptr
。这时会先为基类的成员函数和成员对象分配内存空间,然后再为派生类的自己的成员变量和成员函数分配空间。vptr
会指向Y这个类的vtable
vtable
会多出一行,行内的内容是指向这个新虚函数的函数指针。
class X {
int x;
public:
X() {}
virtual ~X() {}
virtual void printAll() {}
};
class Y : public X {
int y;
public:
Y() {}
~Y() {}
void printAll() {}
};
4. 含有虚函数、有多继承的子类的内存分布
vptr
, 用来指向继承自不同基类的vtable
。也就是说,每一个有虚函数的基类都会有一个虚函数指针表。
class X {
public:
int x;
virtual ~X() {}
virtual void printX() {}
};
class Y {
public:
int y;
virtual ~Y() {}
virtual void printY() {}
};
class Z : public X, public Y {
public:
int z;
~Z() {}
void printX() {}
void printY() {}
void printZ() {}
};
3. 虚函数的内部实现
class X {
public:
int x;
virtual ~X() {}
virtual void printX() { coutprintY(); // OK
y_ptr->printZ(); // Not OK, Y类的虚函数表中没有printZ()函数
y_ptr->y = 3; // OK
y_ptr->z = 3;// not OK, Y类的空间中没有变量z
}
y_ptr
指向的是在Z类对象中的子对象,即Y类对象在Z类中函数与变量。y_ptr
中的_vptr
指向的是Z类对象的vtable
y_ptr->printY()
这行代码,其实会被编译器翻译成如下伪代码((y_ptr->_vptr)->_vtbl[2])();
y_ptr->_vptr
指向Y类对象的vptr
指针,vptr
指针再指向虚函数表中对应的函数指针项,即((y_ptr->_vptr)->_vtbl[2])
, 最后通过函数指针来实现函数调用。_vptr
指向的是Z类对象的虚函数表,所以调用的printY()
函数实际上是Z类中实现的printY()
,即输出"printY() in Z"
。 动态绑定就这样实现了。4. 用几个问题加深理解
Q1. 如果将Z类对象赋值给Y类变量,动态绑定还会发生吗?
"printY() in Z"
还是"printY() in Y"
?Z zz;
Y yy = zz;
yy.printY();
"printY() in Y"
。_vptr
这个指针的赋值操作是在构造类对象的过程中发生的。换一句话说,当一个类的实例被创建的时候_vptr
被赋值,指向该类的vtable
。一旦类的实例被创建,一个类对象里面的_vptr
永远不会变,永远都会指向所属类型的虚函数表。_vptr
还是指向Y类的虚函数表。Q2. 如果在基类中不声明某个函数是虚函数,在子类中重写了这个函数,动态绑定还会发生吗?
"printX() in Z"
还是"printX() in X"
?class X {
public:
int x;
virtual ~X() {}
void printX() { coutprintX(); // OK
}
"printX() in X"
。没有声明为虚函数的函数,不会被放入虚函数表中,即vtable
不会保存该函数的函数指针。这时,动态绑定肯定不会发生了。5. 总结
_vptr
是属于一个类的对象的(one vptr per object).vptr
的值
下一篇:SpringMVC工程搭建