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=  &dd;
	ptr_char->fun_virtual();

	return 0;
}