写了八年 C++,才知道 this 指针竟是这样工作的!从汇编看本质!

开发
今天咱们直接掀开 C++ 的盖子,从汇编代码的角度看看 this 指针到底是个啥玩意儿!我们不仅会了解它的基本概念,还会深入探索它在不同编译器、不同调用约定下的表现,以及它与 C++ 高级特性的关系。

大家好,我是小康。今天我们来聊聊 C++ 的 this 指针。

相信我,看完这篇文章,你将彻底搞懂 C++ 中最神秘的 this 指针!不再被面试官问到 this 时一脸茫然!

如果你对 this 指针还不太熟悉,强烈推荐先看看这篇入门文章:C++ 中的 this 指针:你不知道的 5 个小秘密! 打好基础后,再回来啃这篇硬核分析,绝对事半功倍!

一、前言:this指针,C++中的隐形杀手

嘿,朋友们!还记得第一次接触 C++ 的 this 指针时的懵逼感觉吗?

  • "为啥要用this?"
  • "它到底指向哪里?"
  • "为啥我不写 this 也能访问成员变量?"
  • "编译器是怎么处理这个神秘的指针的?"

如果你还在为这些问题挠头,那这篇文章就是为你准备的!今天咱们不搞那些抽象的概念解释,直接掀开 C++ 的盖子,从汇编代码的角度看看 this 指针到底是个啥玩意儿!我们不仅会了解它的基本概念,还会深入探索它在不同编译器、不同调用约定下的表现,以及它与 C++ 高级特性的关系。

二、this指针的真面目:一个隐藏的函数参数

先说个大实话:this指针其实就是编译器偷偷塞给你的一个函数参数,它指向当前对象的内存地址。

是不是觉得有点懵?没关系,咱们用一个超简单的例子来说明:

class Dog {
public:
    int age;
    
    void bark() {
        cout << "汪汪,我今年" << age << "岁了!" << endl;
    }
};

int main() {
    Dog xiaohua;
    xiaohua.age = 3;
    xiaohua.bark();  // 输出:汪汪,我今年3岁了!
    
    return0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

当我们调用xiaohua.bark()时,编译器实际上做了什么呢?它悄悄地把这个调用转换成了:

// 编译器内部转换后的代码(伪代码)
bark(&xiaohua);
  • 1.
  • 2.

也就是说,编译器偷偷把对象的地址作为第一个参数传给了成员函数!这个隐藏的参数,就是 this 指针!

三、揭秘:从汇编代码看this指针

不信?那我们直接看汇编代码!(别慌,我会用大白话解释)

假设我们有这样一个简单的类:

class Counter {
private:
    int count;
public:
    Counter() : count(0) {}
    
    void increment() {
        count++;
    }
    
    int getCount() {
        return count;
    }
};

int main() {
    Counter c;
    c.increment();
    cout << c.getCount() << endl;
    return0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

我们可以用编译器将这段代码转成汇编语言。以下是在MSVC编译器(VS)编译后的简化汇编代码:

; Counter::increment() 方法的汇编代码(简化版)
?increment@Counter@@QAEXXZ:          ; Counter::increment
    mov eax, ecx             ; ECX寄存器中存储的是this指针
    mov edx, DWORD PTR [eax] ;this->count的值加载到EDX
    add edx, 1               ; count值加1
    mov DWORD PTR [eax], edx ; 将结果写回this->count
    ret                      ; 返回
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

看到了吗?在 Microsoft 的 x86 调用约定中,ECX寄存器被用来存储类成员函数的 this 指针。在这段汇编代码中,ecx就包含了我们对象c的内存地址!

如果我们切换到 G++ 编译器和 Linux 平台,汇编代码可能看起来略有不同:

; G++下的Counter::increment()方法(简化版)
_ZN7Counter9incrementEv:
    mov eax, DWORD PTR [edi]  ;G++中,EDI寄存器存储this指针
    add eax, 1                ; count值加1
    mov DWORD PTR [edi], eax  ; 将结果写回this->count
    ret                       ; 返回
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

有趣的是,不同的编译器和平台对 this 指针的处理方式略有不同。这就是为什么理解底层机制如此重要——它让我们能够更好地理解跨平台编程时可能遇到的问题。

四、深入探索:this指针是怎么传递的?

说到这里,你可能会好奇:"既然 this 是个参数,编译器是怎么传给函数的呢?"

这个问题涉及到所谓的"调用约定"。别被这个术语吓到,简单来说,调用约定就是"函数参数传递的规则",就像不同国家有不同的交通规则一样。

让我用一个简单的比喻:函数调用就像寄快递,调用约定就是快递公司的送货规则:

  • 参数应该放在哪里?(寄存器还是内存栈)
  • 参数应该以什么顺序传递?(从左到右还是从右到左)
  • 谁负责"打扫现场"?(谁来清理栈)

对于 C++ 中的 this 指针,这个"快递"有着特殊的送货方式,而且在不同平台上规则还不一样!

1. 看个实际例子

为了让概念更具体,我们来看一个简单的类和它的成员函数调用:

class Dog {
public:
    int age;
    
    void bark() {
        cout << "汪汪,我今年" << age << "岁了!" << endl;
    }
    
    void eat(int foodAmount, bool isHungry) {
        if (isHungry) {
            cout << "真香!我吃了" << foodAmount << "克狗粮!" << endl;
        } else {
            cout << "我不饿,只吃了" << foodAmount/2 << "克。" << endl;
        }
    }
};

int main() {
    Dog dog;
    dog.age = 3;
    dog.bark();
    dog.eat(100, true);
    return0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

当我们调用dog.bark()和dog.eat(100, true)时,编译器在不同平台上的处理方式有什么不同呢?

2. Windows平台(32位)下this指针的传递

在Windows 32位系统下,MSVC编译器会这样处理:

this指针放在哪里? → ECX寄存器
其他参数怎么传? → 从右到左压入栈中
谁来清理栈? → 被调用函数负责清理栈(称为callee-clean,通过ret N指令实现)
  • 1.
  • 2.
  • 3.

当调用dog.eat(100, true)时,简化的汇编代码会是这样:

; 从右到左压栈,先压入isHungry参数
push 1               ; true
; 再压入foodAmount参数
push 100             ; 100克狗粮
; this指针(dog对象的地址)放入ECX寄存器
lea ecx, [dog]       ; 加载dog的地址到ECX
; 调用函数
call Dog::eat        ; 调用eat方法
; 函数内部会在返回前清理栈
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

3. Linux平台(32位)下this指针的传递

在Linux 32位系统下,G++编译器的处理方式有所不同:

this指针放在哪里? → 作为第一个参数,最后压入栈
其他参数怎么传? → 从右到左压入栈中
谁来清理栈? → 对于普通成员函数,使用的是 cdecl 约定,由调用者清理栈
  • 1.
  • 2.
  • 3.

当调用dog.eat(100, true)时,简化的汇编代码会是:

; 从右到左压栈,先压入isHungry参数 
push 1               ; true
; 再压入foodAmount参数
push 100             ; 100克狗粮
; 最后压入this指针
push [dog的地址]      ; this指针
; 调用函数
call _ZN3Dog3eatEib  ; 调用eat方法,这是G++的名称修饰(name mangling)
; 函数返回后,调用者清理栈
add esp, 12          ; 清理3个参数(each 4字节)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

4. 64位系统下 this 指针的传递

在 64 位系统中,参数传递方式变得更加统一,主要通过寄存器完成,但 Windows 和 Linux 平台使用的寄存器和规则有所不同:

(1) Windows 64位(MSVC编译器):

  • this 指针放在 RCX 寄存器(第一个参数位置)
  • 后续参数分别放在 RDX, R8, R9 寄存器
  • 多余参数(超过4个)才会压栈
  • 谁来清理栈?→ 调用者负责清理栈(通过 add rsp, N 指令来实现)

(2) Linux 64位(G++编译器):

  • this 指针放在 RDI 寄存器(第一个参数位置)
  • 后续参数分别放在 RSI, RDX, RCX, R8, R9 寄存器
  • 多余参数(超过6个)才会压栈
  • 谁来清理栈?→ 调用者负责清理栈(通过 add rsp, N 指令来实现)

以Windows 64位为例,调用dog.eat(100, true)时的简化汇编:

; this指针放入RCX
lea rcx, [dog]       ; 加载dog对象地址到RCX
; foodAmount放入RDX
mov rdx, 100         ; 100放入RDX
; isHungry放入R8
mov r8, 1            ; true放入R8
; 调用函数
call Dog::eat
; 函数返回后,如果有参数通过栈传递,调用者需要清理栈
; 在这个例子中,所有参数都通过寄存器传递,不需要栈清理
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

这里有个有趣的变化:在 32 位系统中,Windows 和 Linux 对 this 指针的处理方式差异很大(寄存器vs栈),而在64位系统中,两者都使用寄存器传递 this 指针,只是使用的具体寄存器不同。

另外,64 位系统无论 Windows 还是 Linux,都使用统一的调用约定,不再像 32 位平台那样对成员函数和普通函数使用不同的约定。这使得 64位 平台下的函数调用机制更加一致和简洁。

五、this指针到底有啥用?实用案例详解

你可能会问:"那我为啥要关心 this 指针啊?又不是我自己写的。"

好问题!this 指针虽然是编译器偷偷加的,但了解它有这些超实用的好处:

1. 区分同名变量

当成员变量和函数参数同名时,this可以明确指向成员变量:

class Person {
private:
    string name;
    int age;
public:
    void setInfo(string name, int age) {
        this->name = name;  // 区分成员变量和参数
        this->age = age;    // 没有this就会造成歧义
    }
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

2. 实现链式编程

返回 this 指针可以实现方法的连续调用,这是很多现代 C++ 库的常用技巧:

class StringBuilder {
private:
    string data;
public:
    StringBuilder& append(const string& str) {
        data += str;
        return *this;  // 返回对象本身
    }
    
    StringBuilder& appendLine(const string& str) {
        data += str + "\n";
        return *this;
    }
    
    string toString() const {
        return data;
    }
};

// 使用方式
StringBuilder builder;
string result = builder.append("Hello").append(" ").append("World").appendLine("!").toString();
// 结果: "Hello World!\n"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

这种编程风格在很多现代框架中非常常见,比如jQuery、C#的LINQ、Java的Stream API等。

3. 在构造函数初始化列表中使用

this指针在构造函数初始化列表中也很有用:

class Rectangle {
private:
    int width;
    int height;
    int area;
public:
    Rectangle(int width, int height) : 
        width(width),       // 参数width赋值给成员变量width
        height(height),     // 参数height赋值给成员变量height
        area(this->width * this->height)  // 使用已初始化的成员计算area
    {
        // 构造函数体
    }
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

注意在初始化列表中,成员变量是按照 声明顺序 初始化的,而不是按照初始化列表中的顺序。上面的例子中,如果 area 在 width 和 height 之前声明,那么计算 area 时使用的 width 和 height 将是未初始化的值!

4. 实现单例模式

this指针在实现单例模式时也非常有用:

class Singleton {
private:
    static Singleton* instance;
    
    // 私有构造函数
    Singleton() {}
    
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
    // 返回this的方法可以链式调用
    Singleton* doSomething() {
        cout << "Doing something..." << endl;
        return this;
    }
    
    Singleton* doSomethingElse() {
        cout << "Doing something else..." << endl;
        return this;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;

// 使用方式
Singleton::getInstance()->doSomething()->doSomethingElse();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

六、汇编角度看不同对象调用同一方法

让我们更进一步,看看不同对象调用同一个方法时,this指针有什么不同:

int main() {
    Dog dog1, dog2;
    dog1.age = 3;
    dog2.age = 5;
    
    dog1.bark();  // 汪汪,我今年3岁了!
    dog2.bark();  // 汪汪,我今年5岁了!
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

从汇编角度来看,这两次调用使用的是完全相同的指令,唯一的区别是传入的 this 指针不同:

; dog1.bark()调用
lea ecx, [dog1]   ; 将dog1的地址加载到ECXthis指针)
call Dog::bark    ; 调用bark方法

; dog2.bark()调用
lea ecx, [dog2]   ; 将dog2的地址加载到ECXthis指针)
call Dog::bark    ; 调用相同的bark方法
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

这就解释了为什么C++可以用同一份成员函数代码处理不同的对象——因为函数通过 this 指针就能知道它正在操作哪个对象!

七、this指针与C++的高级特性

1. this指针与虚函数

虚函数是 C++ 多态的基础,而this指针在虚函数调用中扮演着关键角色

看个简单的多态例子:

class Animal {
public:
    virtual void makeSound() {
        cout << "动物发出声音..." << endl;
    }
};

class Dog :public Animal {
public:
    void makeSound() override {
        cout << "汪汪汪!" << endl;
    }
};

void letAnimalSpeak(Animal* animal) {
    animal->makeSound();  // 调用虚函数
}

int main() {
    Dog dog;
    letAnimalSpeak(&dog);  // 输出:汪汪汪!
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

这里 this 指针有什么作用呢?在虚函数调用中,this指针主要完成两件事:

  • 找到正确的函数地址:当调用animal->makeSound()时,编译器通过 this 指针找到对象的虚函数表,再从表中找到正确版本的函数
  • 传递给实际执行的函数:找到函数后,this指针会作为参数传给它,这样函数才知道它在操作哪个对象

从汇编角度看,虚函数调用大致是这样的:

; animal->makeSound()的汇编实现(简化版)
mov ecx, [animal]    ; 获取this指针
mov eax, [ecx]       ;this指针(ecx)加载vptr(虚表指针)
call [eax + 偏移量]   ; 调用虚表中对应的函数

# 这里的偏移量是虚函数在虚表中的位置。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

这就是为什么letAnimalSpeak(&dog)能正确调用Dog::makeSound()——因为 this 指针指向的是 Dog 对象,所以系统能找到 Dog 的虚函数表,进而调用 Dog 的 makeSound()方法。

this指针让多态成为可能,它确保了同样的代码能根据对象的实际类型执行不同的操作。

2. this指针与const成员函数

在 const 成员函数中,this指针的类型会发生变化:

class Data {
private:
    int value;
public:
    int getValue() const {
        // 在const成员函数中,this的类型是 const Data* const
        // this = new Data(); // 错误!不能修改this指针
        // this->value = 10;  // 错误!不能通过this修改成员
        return value;
    }
    
    void setValue(int v) {
        // 在非const成员函数中,this的类型是 Data* const
        // this = new Data(); // 错误!不能修改this指针
        this->value = v;     // 正确,可以修改成员
    }
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

从编译器角度看,const成员函数相当于:

// 编译器内部转换
int getValue(const Data* const this);  // const成员函数
void setValue(Data* const this, int v);  // 非const成员函数
  • 1.
  • 2.
  • 3.

注意: this 本身总是一个常量指针(const指针),但在 const 成员函数中,它还指向常量对象。

3. this指针与移动语义

在 C++11 引入的移动语义中,this指针同样发挥着重要作用:

class Resource {
private:
    int* data;
    size_t size;
    
public:
    // 移动构造函数
    Resource(Resource&& other) noexcept {
        // 窃取other的资源
        this->data = other.data;
        this->size = other.size;
        
        // 使other处于有效但未定义状态
        other.data = nullptr;
        other.size = 0;
    }
    
    // 移动赋值运算符
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {  // 自赋值检查
            delete[] data;     // 释放自身资源
            
            // 窃取other的资源
            this->data = other.data;
            this->size = other.size;
            
            // 使other处于有效但未定义状态
            other.data = nullptr;
            other.size = 0;
        }
        return *this;  // 返回自身引用,支持链式赋值
    }
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

在移动语义中,this指针用于:

  • 防止自赋值(if (this != &other))
  • 访问和修改当前对象的成员
  • 返回自身引用(return *this)

八、实战例子:手动模拟 this 指针的工作方式

为了彻底理解 this 指针,让我们写个例子,手动模拟编译器的工作:

// 常规C++类
class Cat {
private:
    int age;
    string name;
public:
    Cat(int a, string n) : age(a), name(n) {}
    
    void meow() const {
        cout << name << "喵喵,我" << age << "岁了~" << endl;
    }
    
    void setAge(int a) {
        age = a;
    }
};

// 模拟编译器转换后的代码
struct Cat_Raw {
    int age;
    string name;
};

// 注意第一个参数是Cat_Raw*,相当于this指针
void meow_raw(const Cat_Raw* this_ptr) {
    cout << this_ptr->name << "喵喵,我" << this_ptr->age << "岁了~" << endl;
}

void setAge_raw(Cat_Raw* this_ptr, int a) {
    this_ptr->age = a;
}

int main() {
    // 常规C++方式
    Cat cat(3, "小花");
    cat.meow();  // 输出:小花喵喵,我3岁了~
    cat.setAge(4);
    cat.meow();  // 输出:小花喵喵,我4岁了~
    
    // 手动模拟编译器的方式
    Cat_Raw cat_raw{3, "小花"};
    meow_raw(&cat_raw);  // 输出:小花喵喵,我3岁了~
    setAge_raw(&cat_raw, 4);
    meow_raw(&cat_raw);  // 输出:小花喵喵,我4岁了~
    
    return0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.

看到了吗?两种方式的输出完全一样!这就是 C++ 编译器在背后做的事情——它把对象方法调用悄悄转换成了普通函数调用,而this指针就是这个转换的关键。

九、this指针在不同编程语言中的对比

为了帮助大家更好地理解 this 指针,我们来看看它在不同编程语言中的表现:

1. C++中的this

  • 是指向当前对象的常量指针
  • 隐式传递给非静态成员函数
  • 在成员函数内部可以显式使用,也可以省略
  • 类型为ClassName* const或const ClassName* const(const成员函数)

2. Java中的this

  • 引用当前对象
  • 不能被修改
  • 可以在构造函数中调用其他构造函数:this(args)
  • 也可以用于区分局部变量和成员变量

3. JavaScript中的this

  • 行为更加复杂,由调用方式决定
  • 在全局上下文中,this指向全局对象(浏览器中是window)
  • 在函数内部,this取决于函数如何被调用
  • 箭头函数中的this是词法作用域的this(继承自外部上下文)
function test() {
    console.log(this);  // 在浏览器中,这里的this是window
}

const obj = {
    name: "对象",
    sayHello: function() {
        console.log(this.name);  // 这里的this是obj
    }
};

obj.sayHello();  // 输出:对象

const fn = obj.sayHello;
fn();  // 输出:undefined(因为this变成了全局对象)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

这种对比让我们更加理解 C++ 中 this 指针的特殊性和重要性。

十、this指针的注意事项与陷阱

1. 在静态成员函数中无法使用

静态成员函数属于类而不是对象,所以没有 this 指针:

class Counter {
private:
    static int totalCount;
    int instanceCount;
    
public:
    static void incrementTotal() {
        totalCount++;
        // instanceCount++;  // 错误!静态方法没有this指针
        // this->instanceCount++;  // 错误!静态方法没有this指针
    }
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

2. 在构造函数和析构函数中使用this的注意事项

在构造函数和析构函数中使用 this 时需要格外小心,因为对象可能还未完全构造或已经销毁:

class Dangerous {
private:
    int* data;
    
public:
    Dangerous() {
        data = newint[100];
        registerCallback(this);  // 危险!对象还未完全构造
    }
    
    ~Dangerous() {
        delete[] data;
        unregisterCallback(this);  // 危险!对象已经销毁
    }
    
    void callback() {
        // 如果在构造过程中调用,可能访问未初始化的成员
        // 如果在析构过程中调用,可能访问已销毁的成员
    }
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

3. 返回*this的临时对象问题

使用返回*this的链式调用时,需要注意临时对象的生命周期问题:

class ChainedOps {
public:
    ChainedOps& doSomething() {
        cout << "做点什么..." << endl;
        return *this;
    }
    
    ChainedOps& doSomethingElse() {
        cout << "再做点别的..." << endl;
        return *this;
    }
};

// 安全用法
ChainedOps obj;
obj.doSomething().doSomethingElse();  // 没问题,obj是持久对象

// 需要注意的用法
ChainedOps().doSomething().doSomethingElse();  // 这行代码本身没问题
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

上面第二种用法,初学者可能会担心有问题。实际上在这个简单例子中,根据C++标准,临时对象的生命周期会延长到整个表达式结束,所以这段代码能正常工作。

但如果函数返回的是对临时对象内部数据的引用,就可能有问题:

class Container {
private:
    vector<int> data{1, 2, 3};
    
public:
    vector<int>& getData() {
        return data;  // 返回内部数据的引用
    }
};

// 危险用法
auto& vec = Container().getData();  // 危险!vec引用了临时Container对象中的data
// 此时临时Container对象已被销毁,vec成为悬挂引用
vec.push_back(4);  // 未定义行为!
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

这里的问题是,临时对象Container()在表达式结束后被销毁,但我们保存了它内部数据的引用,这个引用就成了悬挂引用。

所以,关于*this的返回,记住这条简单规则:

  • 返回*this本身一般是安全的
  • 但如果保存了临时对象的成员引用,可能导致悬挂引用问题

十一、总结:揭开this指针的神秘面纱

通过今天的深入探索,我们知道了:

  • this指针就是编译器偷偷塞给成员函数的第一个参数
  • this指针指向调用该成员函数的对象
  • 不同编译器和平台对this指针的传递方式有所不同
  • this指针让不同对象能够共用同一份成员函数代码
  • this指针在C++的高级特性(如多态、const成员函数、移动语义)中扮演着重要角色
  • 从汇编角度看,this实际上就存储在特定的寄存器中(如x86的ECX)
  • 使用this指针时需要注意一些陷阱,尤其是在构造/析构函数中以及返回对象引用时

理解 this 指针的工作原理,不仅能让你写出更清晰、更强大的 C++ 代码,还能帮助你更好地理解面向对象编程的本质!

责任编辑:赵宁宁 来源: 跟着小康学编程
相关推荐

2024-12-12 08:41:28

2025-02-19 08:20:00

编程指针C++

2009-01-18 11:45:57

2021-05-06 10:33:30

C++Napiv8

2021-12-16 13:04:41

消息队列缓存

2011-04-11 11:09:50

this指针

2018-08-01 14:42:07

团队职业工作

2009-08-27 16:03:31

从c#到c++

2021-12-21 15:31:10

C++语言指针

2020-09-20 17:50:38

编程语言PythonJava

2014-01-24 09:49:01

C++指针

2018-09-11 17:40:23

容器数据云计算

2022-06-20 08:56:25

Kafka微服务运维

2016-02-02 17:10:46

戴尔Equal Logic

2024-05-15 16:01:04

C++编程开发

2010-01-26 13:42:28

C++指针

2011-04-19 16:38:00

对象指针指针C++

2011-04-19 09:19:09

C++指针

2011-07-15 01:38:56

C++this指针

2020-11-19 15:21:21

密码网络攻击网络安全
点赞
收藏

51CTO技术栈公众号