请解释为什么以下代码不会崩溃以及如何处理它不会。
class Base
{
public:
Base() {cout << "Base constr" << endl;}
virtual void func() {cout << "Base func()" << endl;}
virtual ~Base() {cout << "Base destr" << endl;}
};
class Layer1 : public Base
{
public:
Layer1() {cout << "Layer1 constr" << endl;}
virtual void func() {cout << "Layer1 func()" << endl;}
virtual ~Layer1() {cout << "Layer1 destr" << endl;}
};
class Layer2 : public Layer1
{
public:
Layer2() {cout << "Layer2 constr" << endl;}
virtual void func() {cout << "Layer2 func()" << endl;}
~Layer2() {cout << "Layer2 destr" << endl;}
};
int main(int argc, char** argv)
{
Layer2 * l2ptr = (Layer2 *) new Base;
l2ptr->func();
delete l2ptr;
return 0;
}
输出:
Base constr
Base func()
Base destr
我的意思是在调用delete l2ptr的时候。从第一次看,似乎应该调用Layer2
析构函数,但是没有创建Layer2
对象。此外,Layer2
析构函数不是virtual
。当我将其设为虚拟时,输出是相同的。为什么呢?
接下来的问题是:通过它的子类指针访问父类对象有哪些问题和注意事项?
编辑:
如果我将Layer2
类更改为此
class Layer2 : public Layer1
{
public:
Layer2() {cout << "Layer2 constr" << endl;}
virtual void func() {cout << "Layer2 func()" << endl;}
void func2() {cout << "Layer2 func2()" << endl;}
virtual ~Layer2() {cout << "Layer2 destr" << endl;}
};
main()
就像这样:
int main(int argc, char** argv)
{
Layer2 * l2ptr = (Layer2 *) new Base;
l2ptr->func();
l2ptr->func2();
delete l2ptr;
return 0;
}
它仍然有效,输出为:
Base constr
Base func()
Layer2 func2()
Base destr
再次,为什么?
答案 0 :(得分:4)
您的代码具有未定义的行为,因此几乎任何关于它为什么执行任何操作的问题都是毫无意义且无法回答的。在某种程度上它完全没有任何特别的东西,它基本上只是运气。
你的东西基本上是你想要的东西(或者你应该在任何情况下应该有的东西)的倒退,这个订单会有main
的东西:
int main(int argc, char** argv)
{
Base *bptr = new Layer2;
bptr->func();
Layer2 *l2ptr = dynamic_cast<Layer2 *>(bptr);
if (l2ptr)
l2ptr->func2();
delete bptr;
}
这里我们使用pointer to Base
来引用Layer2
类型的对象,而不是相反。由于Layer2派生自Base
,因此允许这样做,行为也很有意义。
这让我们可以谈谈为什么会这样做的细节。
析构函数未在virtual
中标记为Layer2
基本上无关紧要:因为它在基类中标记为virtual
,所以在所有派生类中都保持virtual
:
C ++标准,§[class.virtual] / 2:
如果虚拟成员函数vf在类Base和Derived类中声明,直接或间接从Base派生,则成员函数vf具有相同的名称,参数类型列表(8.3.5),cv-声明了Base :: vf的限定和ref-qualifier(或不存在相同),然后Derived :: vf也是虚拟的(无论是否如此声明)并且覆盖 111 Base :: vf。
C ++标准,§[class.virtual] / 6:
即使析构函数不是继承的,派生类中的析构函数也会覆盖声明为virtual的基类析构函数;
答案 1 :(得分:0)
您的代码已经指出了未定义的行为 - 例如,当您通过指向Base
的指针访问Layer2
对象时。
未定义的行为包括出现工作(以及其他任何事情)。在这种情况下,碰巧对象的布局足够相似,没有任何错误。 vtable指针在同一个地方;正确调用虚函数会转到Base
版本,因为该对象是Base
并包含指向Base
vtable的指针。非虚函数转到你告诉编译器对象的类型。
(实现不必使用vtable,但通常会这样做。)
但它仍然是未定义的行为;不要这样做,绝对不要依赖它。
答案 2 :(得分:0)
如果我错了,请纠正我,但你永远不会构建Layer2
或Layer1
的对象;只有一个Base
对象(你用C风格,请注意,而不是C ++)转换为指向Layer2
类型的指针。也就是说,func2
对于指针指向的对象不可用,但编译器会接受它,因为它只检查指针的静态类型。