C++中的虚拟函数困境
Virtual Functions Dilemma in C++
我有两个问题要问。。。
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具有相同的地址,但调用的函数是不同的。函数的定义如何在这里有界?。。。
希望我能尽快得到回复。
C++标准没有提到虚拟表,因此编译器可以自由地以任何方式对其进行优化。在这种情况下,它似乎已经将C
的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
您可以在这里清楚地看到,所有三个成员函数指针都是不同的。
C
的vtable通常与其超类之一(A
或B
(的vtable合并作为优化。但你不应该依赖这个。
如果你想了解幕后发生的事情,这是一本很好的读物:在C++对象模型内部,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
添加的方法,您会注意到,我将其地址放在A
的现有vtable
的末尾:我没有创建一个需要额外vptr
的全新表,而是简单地扩展了现有表。对f2
的调用将简单地获取A_vptr
指向的表中的好地址,并以与其他虚拟方法完全类似的方式调用它。
D
的实例只需要将它们的两个vptr
设置为指向正确的表(一个包含C::f
(因为f
没有被覆盖(和D::f2
的地址,另一个则包含C::f1
的地址(。
以下是我的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
之间有所不同。
请记住,所有这些都是一个实现细节,您永远不应该编写依赖它的代码。
- "error: no matching function for call to"构造函数错误
- 什么时候调用组成单元对象的析构函数
- 继承函数的重载解析
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- C++模板来检查友元函数的存在
- 递归函数计算序列中的平方和(并输出过程)
- 对RValue对象调用的LValue ref限定成员函数
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 为什么使用 "this" 指针调用派生成员函数?
- 将对象数组的引用传递给函数
- 函数调用中参数的顺序重要吗
- 函数向量_指针有不同的原型,我可以构建一个吗
- 使用不带参数的函数访问结构元素
- 代码在main()中运行,但在函数中出现错误
- 内置函数可查看CPP中的成员变量
- 如何获取std::result_of函数的返回类型
- C++简单函数困境
- C++中的虚拟函数困境