首先明确一个空类产生的对象的大小为1B,即使是一个空类,其实例化的对象至少占用1B的内存空间
class A {
};
int main() {
A a;
cout << sizeof(a) << endl; //1
}
然后我们向类A中加入两个普通成员函数,对象的大小还是1B:
class A {
public:
void func1() {}
void func2() {}
};
int main() {
A a;
cout << sizeof(a) << endl; //1
}
这说明A的普通成员函数,并不会占用类对象的内存空间。然而一旦我们向A中加入一个虚函数,其对象的大小变为8B:
class A {
public:
void func1() {}
void func2() {}
virtual void vfunc() {}
};
int main() {
A a;
cout << sizeof(a) << endl; //8
}
原因:当引入虚函数后,编译器在类中插入了一个虚函数表指针vptr,类似于下面的伪码:
class A{
public:
void* vptr;
...
};
而vtpr是占用类对象的内存空间的:
(gdb) p a
$1 = (A) {_vptr.A = 0x7ff771b64520 <vtable for A+16>}
当类A中至少包含一个虚函数,编译器会为类A产生一个虚函数表vtbl
,这个虚函数表会一直伴随着类A,包括其装入内存
虚函数表指针被赋值的时机:执行A的构造函数时,让对象的虚函数指针指向类A的vtbl
每个类对象的虚函数表指针vptr
指向这个类的虚函数表vtbl
,编译器在编译期间在类A的构造函数内安插vptr
的赋值语句,类似于下面的伪码:
class A {
public:
A() {
vptr = &A::vtbl; //编译器做的
...
}
void* vptr;
};
考虑如下的A类对象的内存布局:
class A {
public:
void func1() {}
void func2() {}
virtual void vfunc() {}
virtual void vfunc2() {}
virtual ~A() {}
private:
int m_a;
int m_b;
};
(gdb) p a
$1 = (A) {_vptr.A = 0x7ff77a105520 , m_a = 0, m_b = 1}
注意上面的例子中把A的析构函数设为虚函数,在实际开发中,这样做的目的是保证当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用,否则其只会调用基类的析构函数,如果派生类中有指针成员持有堆区内存,就得不到释放而造成内存泄漏
例如,这是正确的形式:
#include
using namespace std;
class Base {
public:
virtual ~Base() {
cout << "Base dtor" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived dtor" << endl;
}
};
int main () {
Base* pb = new Derived();
delete pb;
return 0;
}
当delete父类指针时,子类的dtor也一同被调用:
$ g++ virtual-dtor.cpp
$ ./a.out
Derived dtor
Base dtor
如果去掉父类dtor前的virtual,则只会调用父类的dtor:
#include
using namespace std;
class Base {
public:
~Base() {
cout << "Base dtor" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived dtor" << endl;
}
};
int main () {
Base* pb = new Derived();
delete pb;
return 0;
}
$ g++ virtual-dtor.cpp
$ ./a.out
Base dtor
多态:当通过父类指针指向子类对象,或通过父类引用绑定子类对象,调用父类中的虚函数,实际调用的是对应子类的虚函数
class Base {
public:
virtual void myvirfunc() {}
};
int main() {
Base* pb = new Base();
pb->vfunc(); //this is polymorphic
Base b;
b.vfunc(); //this is not polymorphic
Base* pb2 = &b;
pb2->vfunc(); //this is polymorphic
}
多态的表现:
下面的调用全是多态调用:
class Base {
public:
virtual void myvirfunc() {}
};
class Derive : public Base {
public:
virtual void myvirfunc() {}
};
int main() {
//父类指针指向子类对象
Derive d;
Base* pb = &d;
pb->myvirfunc();
Base* pb2 = new Derive();
pb2->myvirfunc();
//父类引用绑定子类对象
Derive d2;
Base& rb = d2;
rb.myvirfunc();
}
存在继承关系时虚函数表指针指向:
考虑下面的继承关系和重写:
class Base {
public:
virtual void f() {}
virtual void g() {}
virtual void h() {}
};
class Derive :public Base {
public:
virtual void g() {} //rewrite g()
};
int main(int argc, char* argv[]) {
Base b;
Derive d;
system("pause");
return 0;
}
设父类有f(), g(), h()
这三个虚函数,子类重写了父类中的g()
虚函数,则父子类对象的虚函数表指针和虚函数表如下:
延申思考题:
对于下面的多态实例:
#include
using namespace std;
class ISpeaker {
protected:
int b;
public:
ISpeaker (int bb) : b(bb) {}
virtual void speak() = 0;
};
class Dog : public ISpeaker {
public:
Dog() : ISpeaker(0) {}
virtual void speak() override {
printf("woof %d\n", b);
}
};
class Human : public ISpeaker {
private:
int c;
public:
Human() : ISpeaker(1), c(2) {}
virtual void speak() override {
printf("hello %d\n", c);
}
};
int main(){
ISpeaker* d = new Dog();
ISpeaker* h = new Human();
d->speak();
h->speak();
return 0;
}
#include
using namespace std;
extern "C" {
//虚函数表类型
struct vft {
void (*speak) (void* ptr);
};
//Dog的speak方法
void Dog_speak (void* ptr) {
void* p = ptr + sizeof(vft*);
int bb = *((int*)p);
printf("woof %d\n", bb);
}
//Human的speak方法
void Human_speak (void* ptr) {
void* p = ptr + sizeof(vft) + sizeof(int);
int cc = *((int*)p);
printf("hello %d\n", cc);
}
//Dog的虚函数表,其speak函数指针指向Dog_seapk
const static vft Dog_vft= {
.speak = Dog_speak
};
//Human的虚函数表,其speak函数指针指向Human_speak
const static vft Human_vft = {
.speak = Human_speak
};
//基类型内存模型
struct ISpeaker {
const vft* vptr;
int b;
};
//Dog类型内存模型
struct Dog {
const vft* vptr;
int b;
};
//Human类型内存模型
struct Human {
const vft* vptr;
int b;
int c;
};
Dog* Dog_constructor() {
Dog* d =(Dog*)malloc(sizeof(Dog));
d->vptr = &Dog_vft;
d->b = 0;
return d;
}
Human* Human_constructor() {
Human* h =(Human*)malloc(sizeof(Human));
h->vptr = &Human_vft;
h->b = 1;
h->c = 2;
return h;
}
int main () {
ISpeaker* s1 = (ISpeaker*)Dog_constructor();
ISpeaker* s2 = (ISpeaker*)Human_constructor();
s1->vptr->speak(s1);
s2->vptr->speak(s2);
return 0;
}
}