C++ 虚函数与this指针
this 指针
就是对象的首地址. 无虚函数时,即第1个成员变量的地址. 有虚函数,第 1 个成员变量为修正为 vptr,指向 vtbl 调用成员函数时,如函数里要使用成员变量,编译器根据 this 指针的偏移来寻访各成员变量的值. 这就是成员变量 or 函数都隐含 this 的含义.
vptr 和 vtbl
vptr 指向虚函数表的指针 在构造对象完成后才产生.vptr 将指向一个虚函数表,每个表对应类继承的虚函数实现,如果重写了,则是自己的虚函数实现. 多继承,可能出现多个表,如果自己还有虚函数,则排列在首个 vptr 表的后面 vtbl 则是在编译期间,编译器为有虚函数的类生成. 继承关系复杂了,虚函数多了,虚函数表也好,查表也好,显然都会成为负担.不知业界如何解决的.
小技巧
用 gdb 查看 vtbl.
一个包含多继承的对象
(gdb) p dd
$17 = (Derived_2) {<Base<char>> = {_vptr.Base = 0x4014b0 <vtable for Derived_2+16>, m_b = 4, a = "\000\000\000"}, <Base<int>> = {_vptr.Base = 0x4014d0 <vtable for Derived_2+48>, m_b = 5, a = {0, 255, 0, 0}}, m_d = 6}
查看表:
(gdb) print /a *((void**)0x4014b0)@10
$45 = {0x401136 <Derived_2::fun_virtual()>, 0x401168 <Derived_2::new_fun_virtual()>, 0xfffffffffffffff0,
0x401520 <_ZTI9Derived_2>, 0x401161 <_ZThn16_N9Derived_211fun_virtualEv>, 0x0, 0x401568 <_ZTI4BaseIiE>,
0x40130c <Base<int>::fun_virtual()>, 0x0, 0x401588 <_ZTI7Derived>}
中间还有些奇奇怪怪的地址不知道是什么.
示例加注释
#include <iostream>
#include <string>
#include <assert.h>
using std::cout;
using std::endl;
template <typename T>
class Base {
public:
Base(){};
Base(int b): m_b(b){};
~Base(){};
void fun_base() {std::cout << "base" << std::endl;};
virtual void fun_virtual() {std::cout << "base virtual" << std::endl;};
int getVal() {cout << __func__ << endl; return m_b;};
void setVal(int val) {m_b = val;};
void print_this(){ cout << "This: " << this << endl;}
int m_b;
T a[4];
};
class Derived : public Base<char> {
public:
Derived(){};
Derived(int b, int d):Base<char>(b), m_d(d) {};
~Derived(){};
void fun_derived() {std::cout << "derived" << std::endl;};
void fun_virtual() {std::cout << "derived virtual" << std::endl;};
int getVal() {return m_d;};
int m_d;
};
class Derived_2 : public Base<char>,public Base<int> {
public:
Derived_2(){};
Derived_2(int cb, int cd, int d):Base<char>(cb),Base<int>(cd),m_d(d) {};
~Derived_2(){};
void fun_derived() {std::cout << "derived_2" << std::endl;};
void fun_virtual() {std::cout << "derived_2 virtual" << std::endl;};
int getVal() {return m_d;};
virtual void new_fun_virtual(){std::cout << "derived_2 virtual" << std::endl;};
int m_d;
};
int main(int argc, char* argv)
{
Base<char> b(1);
Derived d(2,3);
Derived_2 dd(4,5,6);
/*
*定义了虚函数的对象,首元素为vptr
*/
cout << "sizeof int: " << sizeof(int) << endl;
//4 + 4 + 8(vptr)
cout << "sizeof b :" << sizeof(b) << endl;
// 4+4+4+8 = 20 再8字节对齐 = 24
cout << "sizeof d :" << sizeof(d) << endl;
/*
* 成员函数调用通过this偏移寻访其他成员变量
* 普通函数调用fun(a,b,c,d);将d,c push stack,a b可能直接寄存器
* 成员函数也是同样的!只不过d,c不明显.
这里包含了一种面象过程到面向对象转变的深刻思想
*/
b.fun_base();
b.fun_virtual();
std::cout << b.getVal() << std::endl;
/*
* bb仍然可以调用Base::func! 只不过push的this不知道是什么东西了
* */
Base<char>* bb = nullptr;
bb->fun_base();
//std::cout << bb->getVal() << std::endl;//不能用
/*
*将子对象d强制转换为父对象b1,实际是通过拷贝构造函数,因此跟vptr无关,
不会覆盖它
*b=d前 内存视图
*b的内存试图
*[0x....0] vptrb = ----->
*[0x....8] m_b = 1
*/
/*
*子类有虚函数实现, vptr为指针,占对象首8字节
*d的内存视图
*[0x....0] vptrd---------->
*[0x....8] m_b = 2
*[0x....c] m_d = 3
*/
/*
*b=d后 内存视图
*b的内存试图
*[0x....0] vptrb = ----->
*[0x....8] m_b = 2
*/
Base<char> b1 = d;
/*
* if no虚函数 this 地址= 1st元素首地址 = 对象取地址
* if has虚函数 this 地址= vptr 地址 = 对象取地址
* */
cout << "Addr of d: " << &d << endl;
d.print_this();
cout << "Addr of 1st elem: " <<&d.m_b << endl;
cout << "Addr of b: " << &b << endl;
b.print_this();
cout << "Addr of 1st elem: " <<&b.m_b << endl;
//调用成员函数时一定是Base::func,编译器都判断好了
b1.fun_base();
//虚函数,从vtbl里找,只有Base::fun_virtual();
b1.fun_virtual();
//拷贝构造过来的 来自d
cout << "copy from d: " << b1.getVal() << endl; //该值来自d!
//Derived d1 = b;//不允许将父类强转为子类
//初始化
Base<char>* ptr= &b;
//ptr指向d, 但对象模型仍然为Base<char>
ptr= &d;
//非虚函数,Base::func_base();
ptr->fun_base();
//vptr就是d的,因此虚函数显然使用的是实际对象d的vptr
ptr->fun_virtual();
cout << "from d: " << ptr->getVal() << endl; //该值来自d!
//引用效果和指针一样
Base<char> & ref = d;
cout << "Ref virtual call same with pointer\n";
ref.fun_virtual();
//但是如果是被赋值过的引用,赋值其实调用了赋值拷贝,还是原来的vptr.
Base<char> & ref1 = b;
ref1 = d;
cout << "Ref using assignment copy not ,vptr keep , virtual not work!\n";
ref1.fun_virtual();
//1 多继承,多个vtbl
//用哪个vtbl,看左边指针是什么原型
//2 继承者又定义了新的virtual函数,应该合并到第1张vtbl表里,而且排在已有的
//虚函数后面
Base<char>* ptr_char= ⅆ
ptr_char->fun_virtual();
return 0;
}