fstream

博客园 首页 新随笔 联系 订阅 管理

1.1 构造函数为什么不能是虚构函数

Avirtual call is a mechanism to get work done given partialinformation. In particular, "virtual" allows us to call afunction knowing only an interfaces and not the exact type of theobject. To create an object you need complete information. Inparticular, you need to know the exact type of what you want tocreate. Consequently, a "call to a constructor" cannot bevirtual. 出处:Why are destructors not virtual by default? 大家谈得最多的是从存储空间角度和使用角度两个方面来解释.个人觉得还可以由内存申请初始化来说明一下.首先我们知道要发生多态必须是指针,或者引用调用(为什么呢? 一会讲) 比如:

struct A{
  virtual void f1(){cout<<"A::f1()"<<endl;}
};
struct B :public A{
  virtual void f1(){cout<<"B::f1()"<<endl;}
};

int main(){
  A *pA = new B;
  (*pA).f1();
  A(*pA).f1();
  B b;
  A(b).f1();
  A *bref=&b;
  bref->f1();
  delete pA;
  return 0;
}

可以尝试回答一下上面都调用了哪些个函数.

new 操作符的执行过程: 1.调用operator new分配内存 ; 2.调用构造函数生成类对象; 3.返回相应指针. 我这里说1,2两步,注意到表达式:new B;首先它不是我们说的多态的指针或引用调用.其次虚函数必定是成员函数,成员函数有个this 指针,如果new 或者对象分配的空间的时候能调用虚构造函数,那么此时的this指向谁?这好像是一个先有鸡还是先有蛋的问题.总之如果构造函数为虚函数,那么new的时候会因为找不到this指针,而没有this指针就根本调用不了虚函数.像这样一种循环依赖,编译器是不好解决的. 顺便提一下成员函数. 他们虽然名字叫成员函数,其实并不在对象的内存布局里面,所谓的内存布局就是比如:

class A{
int m1;
int m2;
void f1(){};
};
A a;

你把变量a想象成一个框,成员变量m1 ,m2 就在框里,但是成员函数就不在这个框里面可是不在框里面编译器怎么找到他的呢,这又回到前一篇讲的名称修饰了,,成员函数在编译器中会被改成类似A::f1@voidxx(A*this)这样的一个函数名,总之就是命名空间,类名,函数名,参数等等这些东西加在一起让它变成一个独一无二的函数名就对了,this是作为第一个参数存在的.

1.2 析构函数为什么在继承的时候要是虚函数

验证这个事实很容易

struct A{
  ~A(){cout<<"destructor A"<<endl;}
};
struct B :public A{
  ~B(){cout<<"destructor B"<<endl;}
};

int main(){
  A *pa = new B;
  delete pa;
  return 0;
}
#+END_SRC c++
知道什么后果了没,,delete只会调用基类A的析构函数.可以同new 对比一下.new 后面跟的是类型,delete跟的是指针,多态是发生在指针身上,如果析构函数为虚,那么多态不会发生,也就是说B的部分不会被析构只会析构基类A的部分,如果反过来呢比如:
#+BEGIN_SRC c++ 
B *pB = new B;
delete pB;

可以看到析构函数按照正确的顺序调用了,说明B的析构函数调用了A的析构函数.结合上一篇我们来分析一下为什么析构函数不为虚的时候就调用不到子类的析构.我们知道虚函数会存在于一个虚表里面,对象会拥有一个虚表指针指向这个表,多态发生时,编译器会根据这个表去查找对应的调用函数,如果析构不为虚,就不在这个表里面了显然不会发生多态, delete 可以理解为pB->~A();既然不为虚,那就直接调用~A()了. 我们来看看虚函数到底是什么样的.工具就用g++,gdb 保存上面代码为virtualdestruct.cc文件,将A的析构变为虚 具体步骤

  1. g++ -g virtualdestruct.cc -o vd
  2. gdb vd
  3. b main
  4. r
  5. n
  6. p *pa;
Thread 1 "v2" hit Breakpoint 1, main () at virtualfunc2.cc:11
11        A *pb = new B;
(gdb) n
12        delete pb;
(gdb) p *pb
$1 = {_vptr.A = 0x403094 <vtable for B+8>}
(gdb) p /a *(void **)0x403094
$2 = 0x4018d0 <B::~B()>

可以看到打印$1 那行,vptr.A指向虚表0x403094,接着再查看这个地址,可以看到第一个是B的虚析构函数.这就是能正确调用析构函数的原因了.

posted on 2019-07-31 15:17  空心柚子哥  阅读(136)  评论(0编辑  收藏  举报