Yas*_*nub 47 c++ gcc this this-pointer
我问自己this指针是否可能被过度使用,因为我通常在每次引用成员变量或函数时都使用它.我想知道它是否会产生性能影响,因为必须有一个指针需要每次都被解除引用.所以我写了一些测试代码
struct A {
int x;
A(int X) {
x = X; /* And a second time with this->x = X; */
}
};
int main() {
A a(8);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
令人惊讶的是,即使-O0他们输出完全相同的汇编程序代码.
此外,如果我使用成员函数并在另一个成员函数中调用它,它会显示相同的行为.那么this指针只是编译时间而不是实际指针吗?或者是否存在this实际翻译和解除引用的情况?我使用GCC 4.4.3 btw.
Sto*_*ica 80
那么这个指针只是编译时间而不是实际指针吗?
这非常属于运行时间.它指的是调用成员函数的对象,当然该对象可以在运行时存在.
什么是编译时间的事情是名称查找的工作原理.当编译器遇到x = X它时,必须弄清楚这x是分配的内容.所以它查找它,并找到成员变量.既然this->x并且x引用相同的东西,自然会得到相同的装配输出.
JBL*_*JBL 28
它是一个实际指针,正如标准所指定的那样(§12.2.2.1):
在非静态(12.2.1)成员函数的主体中,关键字
this是一个prvalue表达式,其值是调用该函数的对象的地址.类的this成员函数的类型X是X*.
this每次在类自己的代码中引用非静态成员变量或成员函数时,实际上都是隐式的.它也是需要的(隐式或显式),因为编译器需要在运行时将函数或变量绑定到实际对象.
显式地使用它很少有用,除非您需要,例如,消除成员函数中的参数和成员变量之间的歧义.否则,没有它,编译器将使用参数隐藏成员变量(在Coliru上查看它).
Mat*_*her 17
this当你处于非静态方法时,总是必须存在.无论您是否明确使用它,您都必须具有对当前实例的引用,这就是this为您提供的.
在这两种情况下,您将通过this指针访问内存.只是在某些情况下你可以省略它.
Pet*_*des 16
这几乎是对象如何在汇编级别的x86中工作?,我在那里评论一些例子的asm输出,包括显示this传入指针的寄存器.
在asm中,其this工作方式与隐藏的第一个arg完全相同,因此成员函数foo::add(int)和非成员add都采用明确的 foo*第一个arg编译为完全相同的asm.
struct foo {
int m;
void add(int a); // not inline so we get a stand-alone definition emitted
};
void foo::add(int a) {
this->m += a;
}
void add(foo *obj, int a) {
obj->m += a;
}
Run Code Online (Sandbox Code Playgroud)
在Godbolt编译器资源管理器上,使用System V ABI(RDI中的第一个arg,RSI中的第二个)编译x86-64,我们得到:
# gcc8.2 -O3
foo::add(int):
add DWORD PTR [rdi], esi # memory-destination add
ret
add(foo*, int):
add DWORD PTR [rdi], esi
ret
Run Code Online (Sandbox Code Playgroud)
我使用GCC 4.4.3
这是在2010年1月发布的,因此它缺少对优化器和错误消息的近十年的改进.gcc7系列已经出现稳定一段时间了.期望使用如此旧的编译器错过优化,特别是对于像AVX这样的现代指令集.
SHR*_*SHR 10
编译后,每个符号只是一个地址,因此它不能成为运行时问题.
即使您没有使用,任何成员符号都会被编译为当前类中的偏移量this.
当name在C用于++它可以是以下中的一个.
::name)中,或在当前命名空间中,或在使用的命名空间中(在using namespace ...使用时)因此,在编写代码时,编译器应以从查找符号名称的方式扫描每个代码,从当前块到全局命名空间.
使用this->name有助于编译器将搜索范围缩小name到仅在当前类范围中查找它,这意味着它会跳过本地定义,如果在类范围中找不到,则不要在全局范围内查找它.
这是一个简单的例子,"this"在运行时如何有用:
#include <vector>
#include <string>
#include <iostream>
class A;
typedef std::vector<A*> News;
class A
{
public:
A(const char* n): name(n){}
std::string name;
void subscribe(News& n)
{
n.push_back(this);
}
};
int main()
{
A a1("Alex"), a2("Bob"), a3("Chris");
News news;
a1.subscribe(news);
a3.subscribe(news);
std::cout << "Subscriber:";
for(auto& a: news)
{
std::cout << " " << a->name;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
你的机器对类方法一无所知,它们是引擎盖下的正常功能.因此,必须通过始终将指针传递给当前对象来实现方法,它只是隐含在C++中,即T Class::method(...)只是语法糖T Class_Method(Class* this, ...).
其他语言(如Python或Lua)选择使其显式化,而像Vulkan(与OpenGL不同)的现代面向对象的C API使用类似的模式.
因为我每次都会使用它来引用成员变量或函数.
你总是使用this,当你引用一个成员变量或函数.没有其他方法可以联系到会员.唯一的选择是隐式符号和显式符号.
让我们回过头来看看它是如何完成的,this以了解它是什么this.
没有OOP:
struct A {
int x;
};
void foo(A* that) {
bar(that->x)
}
Run Code Online (Sandbox Code Playgroud)
使用OOP但this明确写作
struct A {
int x;
void foo(void) {
bar(this->x)
}
};
Run Code Online (Sandbox Code Playgroud)
使用较短的表示法:
struct A {
int x;
void foo(void) {
bar(x)
}
};
Run Code Online (Sandbox Code Playgroud)
但区别仅在于源代码.所有都编译成相同的东西.如果创建成员方法,编译器将为您创建指针参数并将其命名为"this".如果this->在引用成员时省略,则编译器很聪明,足以在大多数时间为您插入它.而已.唯一的区别是源中少了6个字母.
编写this明确有道理当有歧义,即命名就像你的成员变量的另一个变量:
struct A {
int x;
A(int x) {
this->x = x
}
};
Run Code Online (Sandbox Code Playgroud)
有一些实例,比如__thiscall,其中OO和非OO代码在asm中可能会有所不同,但只要指针在堆栈上传递然后从一开始就优化到寄存器或ECX中就不会使它"不"一个指针".