BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / cpp / #16748同步于 2008/11/26
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖

有关纯虚类的析构函数的分析

kissme
2008/11/26镜像同步9 回复
有关纯虚类的析构函数的怀疑与分析。 虽然对这个问题一直比较模糊,但最近写代码,还是让我注意起这个问题来。 就是:纯虚类的析构函数问题。 纯虚类,主要用于接口用,特点是:函数为纯虚函数,不能实例化。通常形式为: class IA { virtual func(type t)=0;} 但我在Linux下编译时,会告诉我:有虚函数却没有虚析构函数的警告。 这是为什么呢?我查了下资料,大概了解了下。这个主要是为继承做考虑的。如: class A{virtual func(type t)=0;} class B : public A {virtual func(type t){};type m_t;} 代码1:A* p = new B; delete p;p=0; 代码2:{ B b;} 你可能会发现代码1和2的执行是有差别的。 代码1 delete的是A型指针,A就要满足两个条件,B的析构才能被执行。 1,A有析构函数,delete A型指针时它就能被执行。 2,A的析构函数必须是virtual的,因为这样子类B的析构才有机会被执行。 当A没有虚析构函数时,分析为代码2的对象b析构被正常执行了,而代码1的却没有。 所以A需要增加一个虚析构函数,并且要有实现体! 这就很讨厌了,用接口,意味着不能让接口类中有函数实现体。 但不用有实现体的虚析构函数,编译会提示我可能存在的风险。 怎么办呢?我必然要用到A* p = new B;的,不然用继承干鸟用啊。 这其间我注意到了其他人代码的一个细节。 就是Release,这种函数会时不时出现在一个SDK或之类的代码用法中。 当时没有理解,还有什么事情需要专门一个函数去干呢。 于是我想到了这样一个做法。 class IA { virtual Release()=0;} class B : public IA {virtual Release(){delete this;};} 代码3:A* p=new B; p->Release();p=0; 这样,代码2和3就等价了。 跑题:为什么接口类不能有实现体。 接口类,通常用作不同模块之间约定好的一个接口表而已,简单地说: 模块A和模块B都知道有一个表是 class C{virtual func1()=0;virtual func2()=0;}; 那么A和B就可以利用这个统一表进行交流。 如果B的表和A的不一样,比如函数顺序颠倒。如: class C{virtual func2()=0;virtual func1()=0;}; 那铁定A和B的交流就会乱套。说白了是使用一样的函数偏移地址。 OK了。那为什么不能有函数实现体呢? 因为实现体会破坏这种单纯表的函数偏移。如果有个func3()有实现体, 那它在A和B模块里所指的数据,变量就是独立生成而不是共用的。 这种数据不会自己产生错误,但容易造成程序员的混乱。 稍用不慎就会导致程序出错。
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
wyx机器人#1 · 2008/11/26
顶了再看,话说很久没见学长了,你每次出来都是技术贴啊,我都想挖你以前的坟了(都是宝藏啊)
kissme机器人#2 · 2008/11/27
【 在 WYX 的大作中提到: 】 : 顶了再看,话说很久没见学长了,你每次出来都是技术贴啊,我都想挖你以前的坟了(都是宝藏啊) 不敢当,事实上我偶尔也在电影和情感天空里发些诸如"婚外初夜"观后感或学生表白分析等 评论性文章.哇哈哈~
windam机器人#3 · 2008/11/27
没明白lz说的这段话是什么意思: “ OK了。那为什么不能有函数实现体呢? 因为实现体会破坏这种单纯表的函数偏移。如果有个func3()有实现体, 那它在A和B模块里所指的数据,变量就是独立生成而不是共用的。 这种数据不会自己产生错误,但容易造成程序员的混乱。 稍用不慎就会导致程序出错。 ” 具体地说不明白的地方是: 为什么一个函数有实现会破坏函数偏移? “变量是独立生成而不是共用的”是什么意思? 如果我这样写会有什么问题呢? class A { virtual ~A() = 0; }; A::~A() {}
kissme机器人#4 · 2008/11/27
【 在 windam 的大作中提到: 】 : 没明白lz说的这段话是什么意思: : “ : OK了。那为什么不能有函数实现体呢? : ................... 我通常把接口类用在不同模块(如DLL)之间的交流. 如果有:class A{static type m_t;} 那么模块1和模块2中生成的A的子类的对象所指的m_t是不同的. 这不会引起编译错误,但容易使程序员混乱.
ericyosho机器人#5 · 2008/11/27
反正最保险的方法,就是不管什么类,都使用虚析构函数。 这个很懒惰,但是也一般能应付了。
windam机器人#6 · 2008/11/27
如果有个func3()有实现体, 那它在A和B模块里所指的数据,变量就是独立生成而不是共用的。 如果有:class A{static type m_t;} 那么模块1和模块2中生成的A的子类的对象所指的m_t是不同的. 这不会引起编译错误,但容易使程序员混乱. ======================== 如果静态变量的话,确实会导致不同模块里分配各自独立的内存。 准确的说,应该是这部分静态变量应该位于全局数据段的某个地方,与具体类实例无关。 但是我的问题还在:为什么不能有函数实现体呢? class A { static type m_t; }; class A { static type m_t; virtual ~A() = 0; }; A::~A() {} 这两种写法在不同模块交流的时候会有什么不同呢?
kissme机器人#7 · 2008/11/27
【 在 windam 的大作中提到: 】 : 如果有个func3()有实现体, : 那它在A和B模块里所指的数据,变量就是独立生成而不是共用的。 : 如果有:class A{static type m_t;} : ................... 由于遇到过我说的问题,使我对函数实现体有些不成熟的猜测. 如果有: class IA{virtual func1(){type t;};virtual func2()=0;}; 我会认为由于{type t;}实现体的存在,会使func2的偏移"不纯". 即它和 class IA{virtual func1()=0;virtual func2()=0;} 是不同的. 确实,我可以让两个模块都包含前者,即一样的代码.并有实现体. 但,我无法确认两个模块的编译环境是一样的. 无法确定 {type t;}经编译后在两个模块中使占的空间是否是一样的. 这会让我对func2的偏移在两模块中是否一致有不信任感.
sunway机器人#8 · 2008/11/27
你知道有虚函数的类都会包含一个vptr么? class Test { public: virtual void fun()=0; virtual void fun2()=0; }; class Test2:public Test { public: void fun() {} void fun2() {} } Test * t=new Test2() 程序运行时内存里有这么几个东西: void __Test2_vfunv() {} 这个函数的地址为A void __Test2_vfun2v() {} 这个函数的地址为B 则t指向的struct Test 只包含一个4byte的vptr指针,这个指针指向一个表,表的第一项可能是类型信息,用来RTTI,第二项可能是A,第三项可能是B,当t->fun时,实际调用的是t.vptr[1] Test类的虚函数有没有实现并不会影响类的大小. class T { int a; }; sizeof(T)=4; class T { int a; public: void fun() {} //函数是放在类之外的,C++的class本质还是用C的struct实现,C的struct怎么把函数放在结构里面? }; size=4 class T { int a; public: virtual fun()=0; virtual fun2(){}; virtual ... }; size=8 class T { }; size=1 【 在 kissme (kissme) 的大作中提到: 】 : 有关纯虚类的析构函数的怀疑与分析。 : 虽然对这个问题一直比较模糊,但最近写代码,还是让我注意起这个问题来。 : 就是:纯虚类的析构函数问题。 : ...................
kissme机器人#9 · 2008/11/27
【 在 sunway 的大作中提到: 】 : 你知道有虚函数的类都会包含一个vptr么? : class Test { : : ................... 噢.受教了.确实我在这个问题有上模糊的认识. 所以引发出来让大家讨论下.以加深我对代码现象的认识. 我觉得各种代码规则和风格总是有原因的而不是无端产生的. 寻找其原因则是一件很有趣的事. 思考题: class IA{virtual Release()=0;} class A:public IA{virtual ~A(){};virtual Release(){delete this;};}; class B:public A{virtual ~B(){};}; class C:public A{B m_b;}; 如果我这么用,能否编译通过? 如果有以下代码,B的析构函数能否被调用? IA* p=new B;p->Release(); IA* p=new C;p->Release();