0

[C++ OOP Thực Chiến] Bài 10: Con trỏ this - Lật tẩy "thế lực ngầm" định vị mọi Object

Chào anh em! Trong suốt 9 bài vừa qua, chúng ta đã gọi các phương thức như user.getName(), wallet.deposit(100) một cách rất vô tư.

Nhưng có một sự thật phũ phàng về cách C++ quản lý bộ nhớ: Dữ liệu (Thuộc tính) của mỗi Object được lưu riêng biệt, nhưng Code (Phương thức) thì chỉ được lưu đúng MỘT BẢN DUY NHẤT trong RAM để tiết kiệm không gian.

Vậy khi bạn gọi user1.getName()user2.getName(), làm sao cái hàm getName() duy nhất đó biết được lúc nào cần moi cái tên của user1, lúc nào cần moi tên của user2? Câu trả lời nằm ở một con trỏ vô hình mang tên: this.

1. Bản chất thật sự của OOP (C++ đã lừa chúng ta)

Thực chất, trong C++, không có khái niệm "Hàm nằm bên trong Object" khi compile xuống mã máy. Trình biên dịch C++ đã ngấm ngầm làm một trò ảo thuật.

Khi bạn viết:

user1.getName();

Trình biên dịch sẽ âm thầm dịch nó thành một hàm kiểu C truyền thống, và tự động nhét thêm một tham số ngầm định (chính là địa chỉ của user1):

// Hàm thật sự mà C++ chạy dưới background
getName(&user1);

Và bên trong định nghĩa của class, C++ tự động cung cấp một con trỏ đặc biệt tên là this để hứng cái địa chỉ đó. this chính là con trỏ trỏ đến cái Object đang gọi hàm.

2. Ứng dụng thực chiến 1: Giải quyết xung đột tên biến (Shadowing)

Trong thực tế đi làm, việc đặt tên biến rất đau đầu. Để code tường minh, tham số truyền vào hàm thường được đặt tên GIỐNG HỆT với tên thuộc tính của Class.

Ví dụ, Class User có thuộc tính age. Hàm setAge nhận vào tham số cũng tên là age. Nếu bạn viết age = age;, trình biên dịch sẽ bối rối không biết ai là ai (thường nó sẽ ưu tiên tham số cục bộ, dẫn đến việc gán vô nghĩa).

Đây là lúc this ra tay cứu giá:

class User {
private:
    int age;
public:
    void setAge(int age) {
        // this->age: Thuộc tính của Object
        // age: Tham số truyền vào từ bên ngoài
        this->age = age; 
    }
};

Cú pháp this-> như một lời khẳng định chắc nịch: "Hãy lấy cái biến age của chính Object đang chạy hàm này!"

3. Ứng dụng thực chiến 2: Tuyệt chiêu "Method Chaining"

Đây là kỹ thuật định hình đẳng cấp của bạn. Hãy tưởng tượng bạn có một hệ thống tạo Profile người dùng. Cách code thông thường (Junior):

User u;
u.setName("Hiếu");
u.setAlias("bố đời");
u.setAge(25);

Code chạy được, nhưng lặp lại chữ u. quá nhiều. Nếu chúng ta làm cho các hàm set này trả về chính cái Object vừa gọi nó, chúng ta có thể nối chúng lại với nhau thành một chuỗi (Chain) cực kỳ thanh lịch.

Để trả về chính Object hiện tại, ta dùng *this (Giải tham chiếu con trỏ this).

4. Code Demo: Thiết kế Builder Pattern cơ bản với this

Hãy xem sự "kỳ diệu" của Method Chaining:

#include <iostream>
#include <string>

using namespace std;

class UserProfile {
private:
    string name;
    string alias;
    int age;

public:
    UserProfile() {
        name = "Unknown";
        alias = "Unknown";
        age = 0;
    }

    // Chú ý kiểu trả về là tham chiếu của chính Class: UserProfile&
    UserProfile& setName(string name) {
        this->name = name; 
        return *this; // Trả về CHÍNH OBJECT này sau khi đã đổi tên
    }

    UserProfile& setAlias(string alias) {
        this->alias = alias;
        return *this; // Trả về CHÍNH OBJECT này
    }

    UserProfile& setAge(int age) {
        this->age = age;
        return *this; // Trả về CHÍNH OBJECT này
    }

    void printProfile() {
        cout << "--- HOSO ---\n"
             << "Ten: " << name << "\n"
             << "Biet danh: " << alias << "\n"
             << "Tuoi: " << age << "\n"
             << "------------\n";
    }
};

int main() {
    UserProfile myProfile;

    // METHOD CHAINING: Cú pháp gọi hàm liên hoàn siêu ngầu!
    // Vì setName() trả về chính myProfile, ta có thể chấm tiếp setAlias(), v.v...
    myProfile.setName("Hiếu")
             .setAlias("bố đời")
             .setAge(25);

    myProfile.printProfile();

    return 0;
}

Nhận xét: Cú pháp nối đuôi nhau này được gọi là Fluent Interface (Giao diện trôi chảy). Các thư viện Backend lớn hoặc các ORM (Object-Relational Mapping) thao tác với Database đều sử dụng triệt để kỹ thuật return *this; để tạo ra các câu query cực kỳ dễ đọc.

Tạm kết & Gợi mở

Tuyệt vời! Con trỏ this đã giúp chúng ta giải quyết gọn gàng bài toán định vị Object trong RAM và mở khóa kỹ năng Method Chaining xịn xò. Nó giúp Object tự thao tác với chính bản thân nó một cách an toàn.

Nhưng trong các nghiệp vụ thực tế, một Object hiếm khi đứng cô lập. Giả sử bạn làm một hệ thống Game, Nhân vật A cần "tấn công" Nhân vật B. Hoặc trong hệ thống Ngân hàng, Tài khoản A cần "chuyển tiền" sang Tài khoản B.

Làm sao để phương thức của một Object có thể tương tác trực tiếp, thay đổi dữ liệu của một Object khác có cùng kiểu Class?

Đây là một bài toán rất thú vị về việc truyền dữ liệu. Hẹn gặp lại anh em ở Bài 11: Phương thức có đối tượng là tham số truyền vào - Nghệ thuật tương tác giữa các thực thể! Đừng quên thả Upvote để mình có động lực gõ phím tiếp nhé!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.