#include <cstdio>
using namespace std;
class A {
public:
virtual void func() { printf("A::func()"); }
};
class B : public A {
public:
virtual void func() { printf("B::func()"); }
};
int main() {
A a = *(A *)new B();
a.func();
}
问题很简单:为什么a->func()
调用类A
中的函数,即使a
包含B类对象?
答案 0 :(得分:13)
A a = *(A *)new B();
a.func();
以下是此代码中一步一步发生的事情:
new B()
:在免费商店中分配类型B的新对象,从而生成其地址(A*)
:对象的地址被强制转换为A*
,因此我们有一个类型为A*
的指针实际指向类型B的对象,该对象是有效的。一切OK。A a
:问题开始了。在堆栈上创建一个新的本地对象,并使用复制构造函数A::A(const A&)
构造,第一个参数是之前创建的对象。new
在免费存储上分配的。a.func()
- 在A类的(本地)对象上调用该方法。如果您将代码更改为:
A& a = *( A*) new B();
a.func();
然后只构造一个对象,其指针将转换为A*
类型的指针,然后取消引用,并使用此地址初始化新的引用。然后,虚拟函数的调用将动态解析为B::func()
。
但请记住,您仍然需要释放该对象,因为它已分配new
:
delete &a;
顺便说一句,只有当A有一个虚拟析构函数时才会正确,这需要B ::〜B()(幸运的是这里是空的,但在一般情况下它不需要)也将被称为。如果A没有虚拟析构函数,那么您需要通过以下方式释放它:
delete (B*)&a;
如果您想使用指针,那么它与引用相同。代码:
A* a = new B(); // actually you don't need an explicit cast here.
a->func();
delete (B*)a; // or just delete a; if A has a virtual destructor.
答案 1 :(得分:5)
您遇到的问题是经典object slicing:
A a = *(A *)new B();
使a
成为A
的引用或指针,虚拟分派将按预期工作。有关详细说明,请参阅this other question。
你评论了另一个答案,“编译器应该至少给出警告或什么”。这就是为什么将基类设为非可复制的抽象被认为是一种好的做法:您的初始代码不会首先编译。
答案 2 :(得分:5)
现在您已修改了代码段,问题很明显。多态性(即虚函数)仅通过指针和引用调用。你没有这些。 A a = XXX
不包含B
类型的对象,它包含A
类型的对象。你通过执行指针转换和取消引用来“切掉”对象的B
-
如果你A *a = new B();
,那么你将得到预期的行为。
答案 3 :(得分:1)
这可能就是这个伎俩。
A &a = *(A *)new B();
a.func();
或者
A *a = new B();
a->func();
答案 4 :(得分:1)
虚拟调度仅适用于指针或引用类型:
#include <cstdio>
using namespace std;
class A {
public:
virtual void func() { printf("A::func()"); }
};
class B : public A {
public:
virtual void func() { printf("B::func()"); }
};
int main() {
A* a = new B();
a->func();
}
答案 5 :(得分:0)
问题是使用A a = *(A *)new B()对B进行A的推理和转换;
您只需删除*(A *)将其更改为(A * a = new B();)即可修复它,但我会更进一步,因为您的变量名称不适合B的实例化。
应该是
B *b = new B();
b->func();
答案 6 :(得分:0)
因为您在将动态分配的对象复制到a
类型的对象A
时执行了切片(这也给了您内存泄漏)。
a
应该是引用(A&
),或者只是保留指针。