欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++面向对象编程之对象模型(objectmodel)关于vptr和vtbl的讲解

程序员文章站 2022-07-09 22:55:41
1. 对象模型(objectmodel):关于vptr和vtbl 注意: 1)C++编译器一旦发现类中有虚函数,就会为该类生成虚函数表,并在该类型的每一个实例中添加一个指向虚函...

1. 对象模型(objectmodel):关于vptr和vtbl

注意:

1)C++编译器一旦发现类中有虚函数,就会为该类生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针(即从内存角度上看,含有虚函数的对象里头会多一个指针)。在32为机器上,一个指针占4字节的内存空间,所以sizeof值为4,。在64位机器上,一个指针占8字节内存,所以sizeof值为8。

2)虚函数表放的是指针(地址)。虚指针关联虚函数,和一般函数无关。

3)C是以静态绑定的方式调用函数的。而C++是以动态绑定的方式调用函数的,C++调用虚函数若以C的风格来写,应该为:

(*(p->vptr)[n])(p);   // 或 (* p->vptr[n])(p); 
4)继承的是函数调用权,父类有虚函数,则子类一定有虚函数。例子:
class A{
public:
	virtual void vfunc1();
	virtual void vfunc2();
	        void func1();
	        void func();
private:
	int m_data1, m_data2;
};

class B:public A {
public:
	virtual void vfunc1();
	        void func2();
private:
	int m_data3;
};

class C:public B{
public:
	virtual void vfunc1();
	        void func2();
private:
	int m_data1, m_data4;
};

5)前面提到C++绑定函数有两个做法,一个是静态绑定call。一个是动态绑定,动态绑定的三个条件是:通过指针调用;向上转型;调用的是虚函数。

6)声明一个容器,里面放指针,指向父类(声明为指向父类的指针,但绑定的(new)是子类)。当子类都有修改某个虚函数,通过遍历容器对象,调用这个虚函数,就能依次调用各自的版本,如下图。(再C里面实现这个行为靠if else else …)。

C++面向对象编程之对象模型(objectmodel)关于vptr和vtbl的讲解

虚函数的这种做法就是“多态”,同样是point toA,但是却实际上指向不同的东西。下面的例子可以解释这个行为(注意这里的this pointer。简单的说,通过对象来调用一个函数,那个对象的地址就是this pointer。所有成员函数都有隐藏参数this

7)关于动态绑定(Dynamic Binding):

(*(p->vptr)[n])(p);  // 或 (* p->vptr[n])(p);

意思是:通过指针找到它的虚指针,再找到它的虚函数表,取出第n个,把它当成函数指针去调用。由于是通过p去调用,因而p是this pointer。(这句话也解释了上面的多态行为)

2.谈谈const

const member functions (常量成员函数)

当对象调用成员函数的时候,对象可能是const也可能不是const,成员函数可能是const也可能不是const,这样就存在两种情况。

规则:当成员函数的const和non-const版本同时存在,const object只会(只能)调用const版本,non-const object只会(只能)调用non-const版本。(而当只有一者存在时,const object也只能调用const的版本。但是non-const object却可能调用const 或non-const 版本。)

下面这个例子是设计的比较不好的:

const String str("hello world");
str.print();    
// 如果当初设计 Srting::print()时未指明 const,
// 那么就是由 const object调用 non-const member function,会出错。 

3.关于new,deete

0)在C++中,设计size_t就是为了适应多个平台的。size_t的引入增强了程序在不同平台上的可移植性。

1)new和delete是表达式,可以分解进去(编译器转化)。

2)重载::operator new, ::operatordelete, ::operator new[], ::operator delete[]

即重载全局函数。没有重载就用全局的。注意delete要接受指针。

3)重载member operator new/delete

接管了内存分配要做什么?做一个内存池。

4)重载member operator new[]/delete[]

接管使用者的行为

示例,接口。。。。。。

如果使用者想绕过设计者的设计,可以加全局的作用域。(写上global scope operator :: 会绕过前述所有overloaded functions,强迫使用global version)

5)重载new(), delete()

placement new:

我们可以重载class member operatornew(), 写出多个版本,前提是每一版本的声明都必须有独特的参数列,其中第一参数必须是size_t, 其余参数以new所指定的placemnet arguments 为初值。出现于new (……) 小括号内的便是所谓的placement arguments。

我们也可以重载class member operatordelete() (或称此为placement operator delete),写出多个版本。但他们绝不会被delete调用。只有当new所调用的ctor抛出exception,才会调用这些重载版的operatordelete()。它只可能这样被调用,主要用来归还未能完全创建成功的object所占用的memory。

这些对应的delete被调用的时机只有一个,那就是对应的new分配内存之后,如果构造函数发挥异常(表示构造对象时失败),那就把刚刚分配的内存释放掉。

6)basic_string 使用new(extra)扩充申请量

(这部分不太理解,后面补上。)