C++ 如何对象保存有关其成员函数的信息
class A {
public :
void printSometext() {
std::cout << "printing A" << std::endl;
}
};
class B {
public :
void printSometext() {
std::cout << "printing B" << std::endl;
}
};
int main() {
A* a = new A();
a->printSometext();
return 0;
}
C++ 对象如何保存有关其成员函数的信息。让我们考虑上面的代码。当我在对象“a”上调用 printSometext 时,它如何知道要调用哪个函数以及如何找到正确的方法。 当打印对象的大小时,它会打印其成员变量的总大小(+对齐)。因此,请提供一些内部信息,了解成员函数调用是如何发生的。
谢谢, 戴姆斯
class A {
public :
void printSometext() {
std::cout << "printing A" << std::endl;
}
};
class B {
public :
void printSometext() {
std::cout << "printing B" << std::endl;
}
};
int main() {
A* a = new A();
a->printSometext();
return 0;
}
How C++ object keeps information about its member functions. Lets consider above code. When I call printSometext on object "a" how does it know what function to call and how it finds the right method.
When printing the size of the object it prints the summing size of its member variable (+allignments). So please provide some internal information how the member function call takes place.
Thanks,
Deimus
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您对 C++ 编程基础知识的理解是错误的。
a
在运行时不知道printSomeText
,编译器和链接器将上述代码转换为执行这些任务的二进制代码。在运行时,只有一堆指令。You have got the basics of C++ programming wrong.
a
does not know aboutprintSomeText
at the runtime, it is the compiler and linker which translates above code to binary code which performs these tasks. At runtime, there is just a bunch of instructions.嗯,这是一个有趣的问题,但让我尝试以非常有条理的方式回答这个问题!
假设编译器必须解析这样的调用:*
*。
现在,编译器将有条不紊地执行以下步骤。
1.) 首先,编译器知道变量a的声明类型,因此它会检查对象a的声明类型是否(
暂时调用这个,类A
) ,有一个名为 someFunc() 的方法,并且它必须是公共的。该方法可以在class A
中声明,也可以是从A类的基类之一派生的方法,但这对编译器来说并不重要,它只是检查它的存在及其访问说明符是public
。2.) 其次,一旦该方法被验证为类 A 的一部分,编译器必须解析对正确方法的调用,因为许多方法可能具有相同的名称(由于函数重载)。这个解析正确方法的过程称为
重载解析
。编译器通过将被调用方法的签名与属于该类的所有重载方法进行匹配来实现此目的。因此,在所有someFunc()
中,只有正确的 someFunc() (与被调用方法的签名相匹配)才会被找到并进一步考虑。3.) 现在困难的部分来了,很可能 someFunc() 可能已在 A 类的一个子类中被重写(
让我们称这个类为 AA,不用说它是 A 的某个子类
),并且该变量 a(声明为 A 类型)实际上可能引用 AA 类的对象(这在 C++ 中是允许的,以实现多态性)。现在,如果 someFunc() 方法被声明为基类(即 A 类)中的虚拟类型,并且 someFunc() 已被 A 的子类(在 AA 或A 和 AA 之间的类),编译器必须找出 someFunc() 的正确版本。现在,假设您是编译器,并且您的任务是查找类
AA
是否具有此方法。显然,类 AA 将具有此方法,因为它是 A 的子类,并且类 A 中 A 的公共访问已经在步骤 1 中由编译器验证了! 。但是,正如上一段所述,someFunc() 可能会被 AA 类(或 A 和 AA 之间的任何其他类)覆盖,这是编译器需要捕获的。因此,您(因为您正在扮演编译器)可以进行系统检查,以找到最底层(继承树中最低的)重写方法 someFunc() 从类 A 开始并以类 AA 结束。在此搜索中,您将查找与重载解析中验证的方法签名相同的方法签名。该方法将是要被调用的方法。现在,您可能想知道,“到底是什么”,这个搜索每次都会进行吗? ... 嗯,并非如此。编译器知道每次查找这个的开销,因此,为每个类类型维护一个名为“虚拟表”的数据结构。将虚拟表视为从方法签名(可公开访问)到函数指针的映射。该虚拟表由编译器在编译过程中创建,并在程序执行期间维护在内存中。在我们的示例中,A 类和 AA 类都有自己的虚拟表。当编译器必须在 AA 类中查找 someFunc() 时(因为变量 a 指向的实际对象是 AA 类型),它只会通过 AA 类的虚表查找函数指针。这与散列到表中一样简单,并且是一个恒定时间的操作。
问候
AViD
Well, that's an interesting question, but let me try to answer that in a very methodical manner !!!
let's say compiler has to resolve a call like this : *
*.
Now, compiler will go over following steps methodically.
1.) Firstly, compiler knows the declared type of variable a, so it will check whether the declared type of object a (
lets call this, class A for time being
), have a method with the name someFunc() and that it must be public. This method could either be declared inclass A
, or it could be a derived method from one of the base class(es) of class A, but it doesn't matter to compiler and it just checks for it's existence with it's access specifier beingpublic
.2.) Secondly, once the method is validated to be a part of the class A, compiler has to resolve the call to the correct method, since many methods could be there with same name (thanks to function overloading). This process of resolving correct method is called
overloading resolution
. Compiler achieves this by matching the signatures of the called method with all the overloaded methods that are part of the class. So, of all thesomeFunc() s
only the correct someFunc() (matching the signatures with called method) will be found and considered further.3.) Now comes the difficult part, It may very well happen that someFunc() may have been overridden in one of the subclasses of the class A (
lets call this class AA and needless to say it is some subclass of A
), and that variable a (declared to be of type A) may actually be referring to an object of class AA, (this is permissible in C++ to enable polymorphism). Now, if the someFunc() method is declared to be of typevirtual
, in base class (i.e. Class A) and someFunc() has been overriden by subclass(es) of A (either in AA or classes between A and AA), the correct version of someFunc() have to be found out by the compiler.Now, imagine you're the compiler and you have this task of finding whether the class
AA
has this method. Obviously, class AA will have this method since, it is a subclass of A and public access of A in class A has already been validated in Step 1 by the compiler !!! . But Alternatively, as mentioned in previous paragraph, someFunc() may be overriden by class AA (or any other class between A and AA), which is what compiler needs to catch. Therefore, you (since, your'e playing the compiler) could do a systematic check to find the bottom-most (lowest in the inheritance tree) overridden method someFunc() starting from class A and ending at class AA. In this search, you'll look for same method signatures as was validated in overloading resolution. This method will be the method which will be invoked.Now, you may be wondering, "What the heck", is this search done everytime ? ... Well, Not really. The compiler knows the overhead of finding this everytime, and as such, maintains a data-structure called
Virtual Table
for every class type. Think of virtual table, as mapping from method signatures (which are publicly accessible) to the function pointers. This virtual table is made by compiler during compilation process and is maintained in-memory during program execution. In our example, class A and class AA will both have their own virtual tables. And when compiler has to be find someFunc() in class AA (since actual object pointed by variable a is of type AA), it will simply find the function pointer through the virtual table of Class AA. This is as simple has hashing into the table and is a constant time operation.Regards
AViD
添加到阿莎的答案(+1):
如果你真的想了解东西内部是如何工作的,我建议学习汇编并探索生成的机器代码。我就是这么做的。
Adding to the Asha's answer (+1):
If you really want to understand how stuff works internally, I suggest to learn assembly and explore the generated machine code. That's what I did.
编译器知道变量的类型。当您使用
。或 ->运算符,它会查找左侧的符号
右侧参数的类类型的上下文。所以在
你的例子,它只找到A::printSomeText。
--
詹姆斯·坎泽
The compiler knows the types of the variables. When you use the
. or the -> operator, it looks up the symbol to the left in the
context of the class type of the right hand argument. So in
your example, it only finds A::printSomeText.
--
James Kanze