返回信息流有关纯虚类的析构函数的怀疑与分析。
虽然对这个问题一直比较模糊,但最近写代码,还是让我注意起这个问题来。
就是:纯虚类的析构函数问题。
纯虚类,主要用于接口用,特点是:函数为纯虚函数,不能实例化。通常形式为:
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模块里所指的数据,变量就是独立生成而不是共用的。
这种数据不会自己产生错误,但容易造成程序员的混乱。
稍用不慎就会导致程序出错。
这是一条镜像帖。来源:北邮人论坛 / cpp / #16748同步于 2008/11/26
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
有关纯虚类的析构函数的分析
kissme
2008/11/26镜像同步9 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
【 在 WYX 的大作中提到: 】
: 顶了再看,话说很久没见学长了,你每次出来都是技术贴啊,我都想挖你以前的坟了(都是宝藏啊)
不敢当,事实上我偶尔也在电影和情感天空里发些诸如"婚外初夜"观后感或学生表白分析等
评论性文章.哇哈哈~
没明白lz说的这段话是什么意思:
“
OK了。那为什么不能有函数实现体呢?
因为实现体会破坏这种单纯表的函数偏移。如果有个func3()有实现体,
那它在A和B模块里所指的数据,变量就是独立生成而不是共用的。
这种数据不会自己产生错误,但容易造成程序员的混乱。
稍用不慎就会导致程序出错。
”
具体地说不明白的地方是:
为什么一个函数有实现会破坏函数偏移?
“变量是独立生成而不是共用的”是什么意思?
如果我这样写会有什么问题呢?
class A
{
virtual ~A() = 0;
};
A::~A() {}
【 在 windam 的大作中提到: 】
: 没明白lz说的这段话是什么意思:
: “
: OK了。那为什么不能有函数实现体呢?
: ...................
我通常把接口类用在不同模块(如DLL)之间的交流.
如果有:class A{static type m_t;}
那么模块1和模块2中生成的A的子类的对象所指的m_t是不同的.
这不会引起编译错误,但容易使程序员混乱.
如果有个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() {}
这两种写法在不同模块交流的时候会有什么不同呢?
【 在 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的偏移在两模块中是否一致有不信任感.
你知道有虚函数的类都会包含一个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) 的大作中提到: 】
: 有关纯虚类的析构函数的怀疑与分析。
: 虽然对这个问题一直比较模糊,但最近写代码,还是让我注意起这个问题来。
: 就是:纯虚类的析构函数问题。
: ...................
【 在 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();