[toc]
this 介绍
this 是C++中在类中可以使用的一个关键字, 表示一个指针, 指向当前类的实例对象。
class Person {
private:
string name;
int age;
public:
Person(string n,int a)
{
name = n;
age = a;
}
Person& add_age(int a)
{
age += a;
return *this;
}
void printInfo() const {
cout << "name: " << this->name << endl;
cout << "age: " << this->age << endl;
}
~Person() {}
};
this 用法
两种常用使用场景
this 与 类的实例强绑定, 决定了this和static无缘。 所以, 在使用this的时候, 不要涉及static相关的内容。
- 在类的非静态成员函数中返回类对象本身的时候,可以使用return *this.
Person& add_age(int a)
{
age += a;
return *this;
}
- 当参数名与成员变量名相同时, 可以使用this来进行区分
void set_name(string name)
{
this->name = name;
}
this指针的类型
在旧版本C++中, this的指向允许被修改, 但是这个特性最终被移除了,现在的this只能作为一个r-value。也正是因为此, this的类型完成了从TypeName * 到 TypeName * const的转换。
如果是在类中的常方法(被const修饰的方法)中, 因为不允许this修改所指向的内容, 因此this指针的类型被转换为const TypeName * const
void printInfo() const {
cout << "name: " << this->name << endl;
cout << "age: " << this->age << endl;
}
this 深究
好, 无聊的基础知识梳理的差不多了, 接下来就开始证道之旅!
this指针何时被赋值?
当实例对象调用成员函数的时候, 会把实例对象的地址隐式传递给this指针。结合代码, 我在关键的汇编代码处加了注释 ,如下:
创建Person类的实例对象的反汇编如下:
44: Person p("Zhansan", 20);
011056FA push 14h
011056FC sub esp,1Ch
011056FF mov ecx,esp
01105701 mov dword ptr [ebp-100h],esp
01105707 push offset string "Zhansan" (0110ED60h)
0110570C call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> > (01101078h)
01105711 lea ecx,[p] //关键步骤一: 这里, 将p的地址直接给了ecx寄存器
01105714 call Person::Person (01101221h)
01105719 mov dword ptr [ebp-114h],eax
0110571F mov dword ptr [ebp-4],0
45: p.add_age(10).printInfo();
函数 add_age 的反汇编代码如下:
26: Person& add_age(int a)
27: {
01105AF0 push ebp
01105AF1 mov ebp,esp
01105AF3 sub esp,0CCh
01105AF9 push ebx
01105AFA push esi
01105AFB push edi
01105AFC push ecx // 这里, ecx保存着p的地址, 然后将ecx的值压栈
01105AFD lea edi,[ebp-0CCh]
01105B03 mov ecx,33h // 这里仍然是操作ecx, 作为一个累计器, 不过此时ecx的值已经在栈中, 这里改变其值不重要
01105B08 mov eax,0CCCCCCCCh
01105B0D rep stos dword ptr es:[edi]
01105B0F pop ecx // 关键步骤二: 这里出栈, 将栈顶的值赋值给了ecx, 也就是p的地址
01105B10 mov dword ptr [this],ecx // 关键步骤三: 这里将ecx的地址赋值给了this
01105B13 mov ecx,offset _CDD4FDA2_this@cpp (0111500Ah)
01105B18 call @__CheckForDebuggerJustMyCode@4 (011013CFh)
28: age += a;
01105B1D mov eax,dword ptr [this]
01105B20 mov ecx,dword ptr [eax+1Ch]
01105B23 add ecx,dword ptr [a]
01105B26 mov edx,dword ptr [this]
01105B29 mov dword ptr [edx+1Ch],ecx
29: return *this;
01105B2C mov eax,dword ptr [this]
30: }
拓展:函数的调用过程
以虚函数举例:
// Your original C++ source code
class Base {
public:
virtual arbitrary_return_type virt0(...arbitrary params...);
virtual arbitrary_return_type virt1(...arbitrary params...);
virtual arbitrary_return_type virt2(...arbitrary params...);
virtual arbitrary_return_type virt3(...arbitrary params...);
virtual arbitrary_return_type virt4(...arbitrary params...);
...
};
- 编译器会创建一个包含5个函数指针的static table , 保存在某块静态内存中。大多数编译器在编译.cpp文件中的Base类的第一个non_inline函数的时候就创建了此static table。我们暂且把这张表叫做v-table, 命名为Base::__vtable. 此static table 是所有类的实例对象共有的. static table结构如下:
// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Base::__vtable[5] = {
&Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4
};
- 编译器将对增加一个隐藏指针到每个实例对象中, 我们把这个隐藏指针叫做v-pointer。 v-pointer是一个隐藏的类成员, 结构如下:
// Your original C++ source code
class Base {
public:
...
FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer
...
};
- 编译器在每个构造函数中对v-pointer进行了初始化, 这样每个实例的对象都会指向这个类的static v-table. 就像下面这样初始化v-pointer:
Base::Base(...arbitrary params...)
: __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer
...
{
...
}
- 如果说有一个派生类Der,继承自Base,编译器将重复步骤1和步骤3(没有步骤2). 在步骤1中, 编译器将创建一个和Base::__vtable一样的static table, 但是会用重载的函数指针替代Base::__vtable中的虚函数指针。假设派生类Der的代码如下:
// Your original C++ source code
class Der:Base {
public:
override arbitrary_return_type virt0(...arbitrary params...);
override arbitrary_return_type virt1(...arbitrary params...);
override arbitrary_return_type virt2(...arbitrary params...);
...
};
派生类Der的v-table就会类似于下面这样:
// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Der::__vtable[5] = {
&Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4
};
在步骤3中, 编译器会在派生类Der的构造器中初始化v-pointer指针, 指向派生类Der的static v-table. Der中的v-pointer指针和Base中的v-pointer指针是同一个。切勿以为,在Der类中又创建了一个v-pointer指针.
- 调用。 我们的调用代码类似于这样:
// Your original C++ code
void mycode(Base* p)
{
p->virt3();
}
编译器不知道你调用的是Base::virt3()还是Der::virt3(), 它只会去当前实例的vtable中去找。 如下:
// Pseudo-code that the compiler generates from your C++
void mycode(Base* p)
{
p->__vptr[3](p);
}
好的, 关于C++的this指针的分享就到这里了。
谢谢大家的阅读。
有问题可以在下面留言。