假设我有以下代码:
class a {
public:
virtual void do_a() = 0;
}
class b {
public:
virtual void do_b() = 0;
}
class c: public a, public b {
public:
virtual void do_a() {};
virtual void do_b() {};
}
a *foo = new c();
b *bar = new c();
foo->do_a()
和bar->do_b()
会有效吗?这里的内存布局是什么?
答案 0 :(得分:4)
a-> do_a()和b-> do_b()会起作用吗?
假设您的意思是foo->do_a()
和bar->do_b()
,因为a
和b
不是对象,它们是类型,是的。他们会工作的。你尝试过那个吗?
这里的内存布局是什么?
这主要是实现定义的。幸运的是,除非你想编写不可移植的代码,否则你不需要知道它。
答案 1 :(得分:3)
他们为什么不应该?内存布局通常类似于:
+----------+
| A part |
+----------+
| B part |
+----------+
| C part |
+----------+
如果您将foo
和bar
转换为void*
并展示它们,那么您将会
得到不同的地址,但编译器知道这一点,并会安排
调用时,this
指针可以正确修复
功能。
答案 2 :(得分:2)
正如其他人所说,以下内容可以正常运行
foo->do_a();
bar->do_b();
然而,这些不会编译
bar->do_a();
foo->do_b();
由于bar
的类型为b*
,因此它不了解do_a
。 foo
和do_b
也是如此。如果你想进行那些函数调用,你必须保持低调。
static_cast<c *>(foo)->do_b();
static_cast<c *>(bar)->do_a();
在示例代码中未显示的另一个非常重要的事物是,在继承时,通过基类指针引用派生类时,基类必须有一个虚拟析构函数。如果没有,则以下内容将产生未定义的行为。
a* foo = new c();
delete a;
修复很简单
class a {
public:
virtual void do_a() = 0;
virtual ~a() {}
};
当然,此更改也需要b
。
答案 3 :(得分:1)
foo->do_a(); // will work
bar->do_b(); // will work
bar->do_a(); // compile error (do_a() is not a member of B)
foo->do_b(); // compile error (do_b() is not a member of A)
// If you really know the types are correct:
C* c = static_cast<C*>(foo);
c->do_a(); // will work
c->do_b(); // will work
// If you don't know the types, you can try at runtime:
if(C* c = dynamic_cast<C*>(foo))
{
c->do_a(); // will work
c->do_b(); // will work
}
答案 4 :(得分:1)
是的,当然会起作用。虽然机制有点棘手。该对象将有两个vtable,一个用于类父类,一个用于类b父类。将调整指针,使它们指向对应于指针类型的对象子集,从而产生这种令人惊讶的结果:
c * baz = new c;
a * foo = baz;
b * bar = baz;
assert((void *)foo == (void *)bar); // assertion fails!
编译器知道赋值时的类型,并确切知道如何调整指针。
这当然完全取决于编译器; C ++标准中没有任何内容表明它必须以这种方式工作。只有它必须工作。
答案 5 :(得分:0)
a-&gt; do_a()和b-&gt; do_b()会起作用吗?
没有
foo->do_a()
和bar->do_b()
会有效吗?
是。您的代码是虚函数调度的典型示例。
你为什么不试试呢?
这里的内存布局是什么?
谁在乎?
(即这是实现定义的,并从您身上抽象出来。您不应该也不想知道。)
答案 6 :(得分:0)
他们会工作。就内存而言,这取决于实现。您已经在堆上创建了对象,对于大多数系统,值得注意的是堆上的对象会向上增长(例如,堆栈向下增长)。所以可能,你将拥有:
Memory:
+foo+
-----
+bar+