"this"指针只是编译时间吗?

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成员函数的类型XX*.

this每次在类自己的代码中引用非静态成员变量或成员函数时,实际上都是隐式的.它也是需要的(隐式或显式),因为编译器需要在运行时将函数或变量绑定到实际对象.

显式地使用它很少有用,除非您需要,例如,消除成员函数中的参数和成员变量之间的歧义.否则,没有它,编译器将使用参数隐藏成员变量(在Coliru上查看它).

  • 当从模板成员访问非依赖基类型的成员时,您还需要显式编写`this->`.不经常需要,一个好的编译器会在你忘记它时准确诊断,但值得一提. (7认同)
  • 从编译器的角度来看,"明确地使用它很少有用",为true; 从人的角度来看,一些团队会将此作为一种风格规则强制执行,以防止人为错误引入错误. (4认同)
  • 在使用IDE进行开发时编写"this->"也非常有用,因为IDE可以提供可供选择的成员列表.(就个人而言,我倾向于不使用IDE,但如果选择使用它,利用它似乎是明智的.) (2认同)

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到仅在当前类范围中查找它,这意味着它会跳过本地定义,如果在类范围中找不到,则不要在全局范围内查找它.


Hel*_*sel 7

这是一个简单的例子,"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)


Tra*_*s3r 7

你的机器对类方法一无所知,它们是引擎盖下的正常功能.因此,必须通过始终将指针传递给当前对象来实现方法,它只是隐含在C++中,即T Class::method(...)只是语法糖T Class_Method(Class* this, ...).

其他语言(如Python或Lua)选择使其显式化,而像Vulkan(与OpenGL不同)的现代面向对象的C API使用类似的模式.


Age*_*t_L 5

因为我每次都会使用它来引用成员变量或函数.

总是使用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中就不会使它"不"一个指针".