这更像是一种好奇心......
假设我有一个C ++类Kitty,如下所示:
class Kitty
{
void Meow()
{
//Do stuff
}
}
编译器是否将Meow()的代码放在Kitty的每个实例中?
显然,到处重复相同的代码需要更多的内存。但另一方面,分支到附近内存中的相对位置需要的编组指令少于分支到现代处理器内存中的绝对位置,因此这可能更快。
我认为这是一个实现细节,因此不同的编译器可能会有不同的表现。
请记住,我不会在这里考虑静态或虚拟方法。
答案 0 :(得分:4)
在通常的实现中,任何给定函数只有一个副本。代码与给定对象实例的数据之间的关联是通过传递一个隐藏参数(在函数中称为this
)来建立的,该参数是指向对象实例的指针(及其数据)。
对于虚函数,事情变得更复杂:每个类都有一个vtable,它包含一组指向虚函数的指针,每个对象都获得一个指向其类的vtable的指针。通过查找vtable指针,查看正确的偏移量以及调用该指针指向的函数来调用虚函数。
答案 1 :(得分:3)
我认为实例方法的标准方法是像任何静态方法一样实现,只执行一次,但是在特定寄存器或堆栈上传递this
指针来执行调用。
答案 2 :(得分:2)
不,这不是它的完成方式
非virtual
的方法与任何其他函数完全相同,但有this
指针的附加参数。
virtual
的方法是invoked using a v-table。 v表是一个函数指针列表,它存储在对象数据旁边。从某种意义上说,这更接近你描述的内容,但是,对于函数的所有实例,函数的主体总是相同的。
如果方法中有static
变量,则可以证明这一点。对于从不同实例调用的方法,静态变量将是相同的。
答案 3 :(得分:2)
由于您在类定义中定义了Meow
,因此Meow
隐式内联。
inline是一个提示编译器用函数的实际内容替换调用的提示。但它只是一个提示 - 编译器可能会选择忽略提示。
如果编译器遵守提示,则每个调用都将替换为函数内容。这意味着编译器将在每次调用Meow
时生成代码,而不是生成函数调用。
如果编译器忽略了提示,编译器/链接器将安排将所有调用定向到的单个版本(因为它是内联的,一个经典的策略是每个使用该函数的翻译单元都将获得一个单独的副本,指示链接器只保留一个版本。)
最后,让我们进入功能不是内联的解释。在这种情况下,编码人员需要确保定义只出现在一个翻译单元中,并且所有呼叫都将被发送到这一个版本。
答案 4 :(得分:1)
不,编译器只为Meow
生成一次代码,每个Kitty
实例都使用该代码,但该成员是在线外编译的。如果编译器能够并选择内联函数,则它在每个使用点(而不是Kitty
的每个实例)都会重复。
答案 5 :(得分:0)
编译器为自己的数据结构中的每个类(而不是对象)创建一个条目。该类的该条目包含指向该类的方法的指针。
一个对象在内存中表示为指向父类的指针及其实例字段的集合(因为它们对于每个对象都是不同的。)然后当调用一个方法时,该对象跟随指向其父对象的指针。跟随指向适当方法的指针。指向该对象的指针也提供给该方法,该方法充当该指针。
虚拟方法稍微复杂一些,但它们也是以类似的方式完成的。
如果您想了解更多信息,请查看您是否可以参加编程语言课程。
这是一个很难尝试ASCII艺术来解释它:
obj class
+------------+ +----------+
| ptrToClass |----------->| method1 | ----------> toSomewhere(ptrToObj)
|------------| |----------|
| field1 | | method2 | ----------> toSomewhereElse(ptrToObj)
+------------+ +----------+