C++中的“this”指针是何时被初始化的?

12

嗨,我有一个关于this指针的问题,当一个对象被构造时,它是在什么时候被初始化的?也就是说,我什么时候可以使用它?虚表在构造函数中被构建,这和this指针是一样的吗?

例如,我有这样的代码。输出结果为8。这是否意味着在进入构造函数之前,this指针已经被初始化了?

class A{
   public:
     A() { cout<<sizeof(*this);}
     int i;
     int *p; 
};

int main() {      
    A a; 
}

如果是真的,那在进入构造函数之前还会发生什么?

如果不是真的,那么什么时候初始化 this 指针?


5
编译器知道*this的大小,因此会生成硬编码的值。也许你需要一个更好的例子。 - Hans Passant
2
sizeof是一个编译时的东西(我不会说是硬编码),并且不依赖于运行时“this”指针中包含的值。此外,vtable(如果有)不是在构造函数中构建的。 - anon
对尼尔:真的吗?如果不是在构造函数里,那是什么? - skydoor
虚函数表(vtable)在编译时已知并构建,在运行时加载。 实例指向虚函数表的指针(instance-vptr)在构造函数调用之前分配。该赋值由编译器生成的基础设施完成。 - abelenky
8个回答

15

this指针不是对象或类的成员 - 它是您调用的方法的隐式参数。因此,它像任何其他参数一样传递 - 只是您不直接请求它。

在上面的示例中,构造函数是一种特殊的方法,它又是一种特殊类型的函数。当您构造对象时,编译器为其分配内存(在本例中为堆栈上,因为amain函数中的局部变量)。然后它自动调用构造函数来初始化对象。

作为调用构造函数的一部分,隐式参数this - 指向您的对象的指针 - 作为参数传递。

在具有以下签名的方法中...

void MyMethod (const int* p) const;

实际上有两个参数,都是指针。显式参数是p,隐式参数是this。行末的const指定this是一个const指针,就像早先指定p是一个const指针一样。需要特殊语法仅因为this是隐式传递的,所以你无法像其他参数一样以正常方式指定 const。

"静态"方法没有隐式的"this"参数,也不能直接访问对象成员-可能不存在与调用相关联的特定对象。它基本上是一个标准函数而不是方法,除了可以访问私有成员(只要能找到要访问的对象)。

正如Steve Fallows指出的,sizeof (this)在编译时已知,因为它是一个指针类型,所有指针(*1)具有相同的sizeof值。你看到的"8"意味着你正在为64位平台编译。此时this可用-它指向有效的内存,并且所有成员已经完成了构造函数的调用。但是,它并不一定完全初始化-毕竟,你还在构造函数调用中。

编辑

*1 - 严格来说,这可能并不是真的 - 但编译器知道它在处理什么类型的指针,即使值直到运行时才知道。


+1,讲解得非常好。在调用约定的上下文中,“this”指的是什么:http://msdn.microsoft.com/en-us/library/984x0h58%28v=VS.71%29.aspx - Janusz Lenar

7

this指针不会被存储。当为占用特定内存位置的对象调用构造函数时,该位置作为参数传递给构造函数和其他成员函数。

如果this指针会被存储在对象内部,那么如何检索该指针呢?没错,你需要再次使用this指针 :)


只是为了加一些解释,以防需要 - 当您在方法内访问对象的成员时,编译器在幕后使用this来查找对象和成员。 member1this->member1实际上是相同的东西 - 第一种形式只是一种简写。 - user180247
+1 对于你的最后一句话让我陷入了无限递归。 - Dario

2

sizeof(*this)在编译时已知。因此,cout语句对于this的初始化没有任何作用。

考虑到构造函数可以立即开始访问对象的成员,很明显,this在构造函数开始之前已经被初始化。

在构造函数之前还会发生什么?可能是任何事情。我不认为标准限制编译器可以做什么。也许您应该指定任何您认为可能发生的事情。


2
虚函数表不是在构造函数中构造的。
通常,相同类的所有实例共享单个全局虚函数表,每个单独的类都有自己的全局虚函数表。
虚函数表在编译时已知,并且在程序加载时“构建”。
this指针是在分配时“构造”(我认为“分配”是一个更好的术语),也就是在调用全局 new 操作符之后,在进入构造函数之前分配。
如果对象是堆栈分配而不是堆分配,则不会调用全局new,但是this仍然可用,因为堆栈空间分配是在进入构造函数之前进行的。
对象的实例vptr在内存分配后、构造函数调用之前分配。

2
vtable不是在那里构造的,但需要填充vtable指针(vptr)。 - kennytm
编辑以提及vptr的分配,该分配发生在分配之后,构造之前。 - abelenky
所有这些都假定存在一个虚函数表 - 编译器可以以任何方式实现运行时分派,只要它达到了预期的结果。这可能意味着在运行时修改某种数据结构以使最常见的调用运行更快(也许是为了理论上的目的),但很难超越虚函数表的效率。 - user180247
this 是每个(非静态)方法的参数。它从未被“构造”或“分配”。创建对象首先会分配足够大的内存块,并将一个指针传递给最终派生类型的构造函数。请注意,在第一种情况下,this 是指向分配的内存块开头的指针,但是在存在多重继承的情况下,this 可能不是指向分配内存块开头的指针:struct b1 { int x; b1(); }; struct b2 { int x; b2(); }; struct d : b1, b2 {} 传递给 b1b2 构造函数的 this 指针是不同的。 - David Rodríguez - dribeas
这意味着,在某些情况下,this将与分配的指针重合,而在其他一些情况下则不会。即使在许多情况下它会重合,但this不需要是分配的结果。 - David Rodríguez - dribeas
@abelenky:在构造函数/析构函数中调用纯虚函数是被禁止的。我猜测这是因为vptr尚未/已经没有重定向到派生类。我错了吗? - Janusz Lenar

2
“在构造函数被调用之前,这个指针已经被初始化了”是什么意思?
是的,构造函数被调用之前,this指针的值就已经确定了。在构造函数、构造函数初始化列表、析构函数和成员方法中,可以通过this关键字访问该值。this关键字表面上看起来像一个方法变量(指针类型),但它实际上不是一个变量;它通常位于一个寄存器中(x86平台上为ecx),您通常无法编译像&this这样的代码。
还有什么其他事情会在构造函数被调用之前发生呢?
至少就 this 指针而言,第一件事情是(除非使用 定位 new分配内存,最终由该指针指向的内存可以在堆栈上(例如您的示例中)或堆上(使用 new)进行分配。此时已知 this 指针。然后,对于基类(如果有的话)和您的类非 POD 成员变量,将调用默认构造函数或通过构造函数初始化列表显式指定的构造函数。如果您的类包含虚拟方法或析构函数,则此时还会设置类的 vtable 指针。然后,如果有的话,会调用类构造函数主体。(构造函数是递归调用的,即当调用基类的构造函数时,首先调用基类的基类构造函数,然后是非-POD 成员构造函数,在基类的 vtable 指针设置之后,再调用类的构造函数主体。)

0

this 指针在进入构造函数体之前就已经指向了当前对象,并且所有成员和基类都已经初始化。

因此,在初始化列表中可以传递 this 指针,但接收者除了存储它之外不应该做任何其他操作,因为所指向的实例可能在此时尚未完全构造。

#include <iostream>

class B;

class A
{
    B* b_ptr;
public:
    A(B* b);
};

class B
{
    A a;
    int i;
public:
    B(): a(this), i(10) {}
    void foo() const { std::cout << "My value is " << i << '\n'; }
};

A::A(B* b):
    b_ptr(b) //Ok to store
{
    b_ptr->foo(); //not OK to use, will access initialized member
}

int main()
{
    B b;
}

0

this指针是每次调用类的第一个参数,包括构造函数。

当调用类方法时,类的地址最后被推入堆栈(假设这里使用cdecl调用约定)。然后将其读回到寄存器中,以用作this指针。

实际上,构造函数被调用时就像普通成员函数一样。

你不能有虚构造函数,因为构造函数负责设置vtable成员。


以上某些或全部内容可能适用于您特定的编译器,但是您所说的内容都没有被C++标准规定。 - anon

0

正如nobugz所指出的那样,您的示例并没有什么意义 - sizeof基于您传递给它的对象的类型产生结果。它在运行时评估其操作数。

话虽如此,是的,在进入构造函数之前this已经初始化。基本上,编译器为该对象分配空间(如果该对象具有自动存储期限,则在堆栈上,如果具有动态存储期,则使用::operator new)。进入构造函数时,基类的构造函数(如果有)已经全部运行完毕。当调用您的构造函数时,this会给出为该对象分配的内存的地址。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接