我有两个问题要问......
a)
Class A{
int a;
public:
virtual void f(){}
};
Class B {
int b;
public:
virtual void f1(){}
};
Class C: public A, public B {
int c;
public:
virtual void f(){} // Virtual is optional here
virtual void f1(){} // Virtual is optional here
virtual void f2(){}
};
Class D: public C {
int d;
public:
void f2(){}
};
现在C ++说在C的实例中不会有3个虚拟指针但只有2个。然后,如何调用说,
C* c = new D();
c->f2();
//由于没有与f2()中定义的虚函数对应的虚拟指针。后期绑定是如何完成的?...
我读到说,这个函数的虚拟指针被添加到第一个超类C的虚拟指针中。为什么会这样?...为什么没有虚拟表?...
的sizeof(* C); //这将是24而不是28 ..为什么?...
另外说,考虑到上面的代码,我这样做,
void (C::*a)() = &C::f;
void (C::*b)() = &C::f1;
printf("%u", a);
printf("%u",b);
// Both the above printf() statements print the same address. Why is that so ?...
// Now consider this,
C* c1 = new C();
c1->(*a)();
c1->(*b)();
//尽管a和b具有相同的地址,但调用的函数是不同的。这个函数的定义如何在这里界定?...
希望我能尽快得到回复。
答案 0 :(得分:2)
C ++标准没有提及虚拟表,因此编译器可以以任何方式自由地优化它。在这种情况下,似乎已将C
的vtable与其中一个vtable合并,但这当然不是必需的。 需要的是,如果你这样做:
C* c = new D();
c->f2();
它调用D::f2
,因为它在C
中是虚拟的。
不允许将成员函数指针转换为void*
,更不用说unsigned
所以毫不奇怪它们可能无法在printf
中以预期的方式打印(只读取要打印的原始字节)。原因是使用%u
,你对printf撒谎,告诉它在你实际传递一个完全不是int
的参数时打印一个int。换句话说,a
和b
成员函数指针实际上是不同的,尽管printf
似乎告诉你。由于它们真的不同,因此它们正常工作并不奇怪。
如果你想尝试打印编译器给你的真实函数指针,那么“最便携”的方法是memcpy
将它变成unsigned char
的向量然后打印出来。冗长的例子:
#include <iostream>
#include <vector>
class Foo
{
public:
virtual void f1() { }
virtual void f2() { }
void f3() { }
};
int main()
{
void (Foo::*a)() = &Foo::f1;
void (Foo::*b)() = &Foo::f2;
void (Foo::*c)() = &Foo::f3;
std::cout << a <<std::endl;
std::cout << sizeof(a) << std::endl;
std::cout << b <<std::endl;
std::cout << sizeof(b) << std::endl;
std::cout << c <<std::endl;
std::cout << sizeof(c) << std::endl;
std::vector<unsigned char> a_vec(sizeof(a));
memcpy(&a_vec[0], &a, sizeof(a));
for(size_t i = 0; i < sizeof(a); ++i)
{
std::cout << std::hex << static_cast<unsigned>(a_vec[i]) << " ";
}
std::cout << std::endl;
std::vector<unsigned char> b_vec(sizeof(b));
memcpy(&b_vec[0], &b, sizeof(b));
for(size_t i = 0; i < sizeof(b); ++i)
{
std::cout << std::hex << static_cast<unsigned>(b_vec[i]) << " ";
}
std::cout << std::endl;
std::vector<unsigned char> c_vec(sizeof(c));
memcpy(&c_vec[0], &c, sizeof(c));
for(size_t i = 0; i < sizeof(c); ++i)
{
std::cout << std::hex << static_cast<unsigned>(c_vec[i]) << " ";
}
std::cout << std::endl;
return 0;
}
在g ++ 4.2上,这会产生:
1
8
1
8
1
8
1 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0
c6 1d 5 8 0 0 0 0
你可以清楚地看到,所有三个成员函数指针都是不同的。
答案 1 :(得分:1)
C
的vtable通常与其超类之一(A
或B
)的vtable合并作为优化。但你不应该依赖于此。
答案 2 :(得分:1)
如果您想了解幕后发生的事情,那么这是一个很好的阅读:Inside the C++ Object Model, de Stanley Lippman。内容开始显示其年龄,但它提供了一些全面的演示,这些技术曾经(有时仍然)用于实现C ++特性,如继承,多态,模板等。
现在,回答您的问题:首先,您应该知道供应商必须实现给定功能的方式通常不是由C ++标准指定的。这就是这种情况:根本不需要实现虚拟方法表(即使它们经常使用)。
话虽这么说,我们仍然可以尝试猜测这里发生了什么。首先,让我们看看如果我们创建了一个A
实例,内存会是什么样的:
A someA;
________________ ----------------
| @A_vtable | vptr -------->| @A::f |
________________ ----------------
| [some value] | a A_vtable
________________
someA
除了成员变量之外,您还可以看到A
的实例包含虚拟表指针(vptr
)。此vptr
指向A
的虚拟表,其中包含A
f
实现的地址。
B
的实例应该非常相似,所以我不打算画一个。现在让我们看看C
实例的样子:
C someC;
________________ ------->----------------
| @C_A_vtable | A_vptr / | @C::f |
________________ ----------------
| [some value] | a | @C::f2 |
---------------- ----------------
| @C_B_vtable | B_vptr \ C_A_vtable
________________ \
| [some value] | b \
________________ \
someC ---->----------------
| @C::f1 |
----------------
C_B_vtable
您可以看到someC
包含A
部分和B
部分,两者都包含vptr
。这样,我们就可以通过在类中使用偏移量将C
转换为A
或B
。现在,关于C
添加的方法,您会注意到我将其地址放在vtable
的现有A
的末尾:而不是创建一个全新的表,这将需要一个额外的vptr
,我只是扩展了现有的一个。对f2
的调用只会获取A_vptr
指向的表中的好地址,并以与其他虚拟方法完全相似的方式调用它。
D
的实例只需将两个vptr
设置为指向正确的表(一个包含C::f
的地址(因为f
未被覆盖)和D::f2
,另一个包含C::f1
的地址。
答案 3 :(得分:0)
以下是我的Visual C ++ 2010如何在内存中列出这些类的对象:
object_a {a=-858993460 } A
__vfptr 0x009d5740 const A::`vftable' *
[0] 0x009d11f9 A::f(void) *
a -858993460 int
object_b {b=-858993460 } B
__vfptr 0x009d574c const B::`vftable' *
[0] 0x009d1203 B::f1(void) *
b -858993460 int
object_c {c=-858993460 } C
A {a=-858993460 } A
__vfptr 0x009d5764 const C::`vftable'{for `A'} *
[0] 0x009d108c C::f(void) *
a -858993460 int
B {b=-858993460 } B
__vfptr 0x009d5758 const C::`vftable'{for `B'} *
[0] 0x009d10a5 C::f1(void) *
b -858993460 int
c -858993460 int
object_d {d=-858993460 } D
C {c=-858993460 } C
A {a=-858993460 } A
__vfptr 0x009d5780 const D::`vftable'{for `A'} *
[0] 0x009d108c C::f(void) *
a -858993460 int
B {b=-858993460 } B
__vfptr 0x009d5774 const D::`vftable'{for `B'} *
[0] 0x009d10a5 C::f1(void) *
b -858993460 int
c -858993460 int
d -858993460 int
如您所见,多重继承为每个类型生成多个虚拟表,并且每个对象生成多个虚拟表指针。
基于此,您的问题的答案如下:
c->f2(); // Since there is no virtual pointer corresponding to the virtual function defined in f2(). How is the late binding done ?.
编译器知道C
的布局,因此它知道使用第二个__vfptr
以及C::f1
在该表中的偏移量。
sizeof(*c); // It would be 24 and not 28.. Why ?...
在我的系统上(32位版本中):
sizeof(C)
== sizeof(__vfptr) + sizeof(a) + sizeof(__vfptr) + sizeof(b) + sizeof(c)
== 4 + 4 + 4 + 4 + 4
== 20
显然,你的编译器做了不同的事情。
void (C::*a)() = &C::f;
void (C::*b)() = &C::f1;
printf("%u", a);
printf("%u", b);
// Both the above printf() statements print the same address. Why is that so ?...
因为它们是成员函数指针,而不是普通的函数指针。实施细节各不相同,但这些可能是小结构甚至是thunk。显然,在这种情况下,两个函数调用都被相同的结构或thunk“覆盖”,但是成员指针的单独“部分”通过printf
不可见,并且在a
之间不同和b
。
请记住,所有这些都是一个实现细节,您永远不应该编写依赖它的代码。