+4

[C++ OOP Thực Chiến] Bài 15: Tham chiếu (Reference) là gì? - Sợi dây liên kết sống còn trong C++

Chào anh em! Trong [Bài 11], khi làm tính năng tấn công (attack), mình đã nhấn mạnh việc phải thêm dấu & (Tham chiếu) vào tham số truyền vào: void attack(Character& target).

Nếu thiếu đi dấu & nhỏ bé đó, thay vì trừ máu con Boss thật, hệ thống sẽ tự động tạo ra một "bản sao" của con Boss, trừ máu bản sao đó, và con Boss thật vẫn nhăn nhở cười.

Trong giới C++, có một câu nói: "Nếu không hiểu về Tham chiếu, bạn đừng bao giờ nhận mình biết code C++". Hôm nay, chúng ta sẽ mổ xẻ bản chất của "sợi dây" ma thuật này.

1. Bản chất của Tham chiếu: "Bí danh" (Alias)

Khái niệm Tham chiếu cực kỳ đơn giản: Nó là một cái tên khác (bí danh) của một biến đã tồn tại. Nó KHÔNG phải là một biến mới, nó KHÔNG tốn thêm RAM để lưu trữ.

Hãy lấy một ví dụ đời thực: Giả sử bạn tên thật là Hiếu. Khi lên mạng xã hội hoặc vào các group chat, anh em gọi bạn bằng bí danh là bố đời. Dù người ta gọi "Hiếu" hay gọi "bố đời", thì cũng là đang gọi chính bạn. Nếu "bố đời" bị trừ 50k trong ví, thì ví của "Hiếu" cũng mất đúng 50k đó.

Trong C++, cú pháp để tạo bí danh là dùng dấu & ngay sau kiểu dữ liệu:

int hieu_wallet = 500;
int& bodo_wallet = hieu_wallet; // Tạo tham chiếu (bí danh)

bodo_wallet -= 50; // Trừ tiền của bí danh
// Lúc này hieu_wallet cũng chỉ còn 450!

2. Tham chiếu (Reference) khác gì Con trỏ (Pointer)?

Nhiều anh em học C ngày xưa sẽ thắc mắc: "Thế thì nó khác quái gì Con trỏ (*)?". Về mặt bản chất bên dưới mã máy, Tham chiếu thực chất là một Con trỏ đã được C++ "làm đẹp" lại cho an toàn hơn:

  1. Sinh ra là phải có chủ: Bạn có thể tạo con trỏ rỗng (int* ptr = nullptr;), nhưng bạn KHÔNG THỂ tạo tham chiếu rỗng (int& ref; -> Lỗi ngay). Tham chiếu khi khai báo bắt buộc phải được gán ngay cho một biến cụ thể.
  2. Chung thủy tuyệt đối: Con trỏ hôm nay trỏ biến A, ngày mai có thể trỏ biến B. Nhưng Tham chiếu một khi đã nhận "Hiếu" làm chủ, nó sẽ là bí danh của "Hiếu" suốt đời, không thể đổi sang người khác được.
  3. Cú pháp sạch sẽ: Dùng tham chiếu thao tác y hệt như biến bình thường, không cần xài dấu sao (*) giải tham chiếu hay mũi tên (->) lằng nhằng.

3. Ứng dụng thực chiến: Tránh Copy dữ liệu lớn

Đây là lý do các Backend Engineer yêu thích Tham chiếu. Hãy tưởng tượng bạn đang xử lý một Object Transaction (Giao dịch) chứa hàng tá thông tin phức tạp.

#include <iostream>
#include <string>

using namespace std;

class Transaction {
private:
    string id;
    string payload; // Chứa hàng MB dữ liệu JSON
    string status;

public:
    Transaction(string id) : id(id), payload("{... Huge Data ...}"), status("PENDING") {}
    
    string getStatus() const { return status; }
    
    // Setter
    void setStatus(string newStatus) { status = newStatus; }
};

// CÁCH 1: PASS-BY-VALUE (Thiếu kinh nghiệm)
// C++ sẽ COPY toàn bộ object Transaction (tốn RAM, tốn CPU)
// Tồi tệ hơn: Nó chỉ update status trên BẢN COPY, bản gốc bên ngoài không suy suyển!
void processPaymentBad(Transaction tx) {
    tx.setStatus("SUCCESS"); 
}

// CÁCH 2: PASS-BY-REFERENCE (Chuẩn Senior)
// tx bây giờ chỉ là BÍ DANH của Object truyền vào.
// KHÔNG COPY, tốc độ xử lý tức thì, và update thẳng vào bản gốc!
void processPaymentGood(Transaction& tx) {
    tx.setStatus("SUCCESS");
}

int main() {
    Transaction myTx("TX_12345");

    cout << "Status ban dau: " << myTx.getStatus() << "\n";

    // Chạy thử hàm "Bad"
    processPaymentBad(myTx);
    cout << "Sau khi chay ham Bad: " << myTx.getStatus() << " (Van chua thanh cong!)\n";

    // Chạy thử hàm "Good"
    processPaymentGood(myTx);
    cout << "Sau khi chay ham Good: " << myTx.getStatus() << " (Ngon lanh!)\n";

    return 0;
}

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

Xuyên suốt các bài gần đây, quy tắc sống còn mà chúng ta rút ra là: Luôn luôn dùng Tham chiếu (&) để truyền Object vào hàm nhằm tránh việc C++ copy dữ liệu vô tội vạ. (Nhớ kèm theo const nếu hàm đó chỉ để đọc dữ liệu).

Nhưng khoan đã... Trong thực tế, không phải lúc nào chúng ta cũng "sợ" việc copy. Sẽ có những nghiệp vụ bạn THỰC SỰ MUỐN copy một Object ra thành nhiều bản.

Ví dụ: Trong một game bắn súng, khi nòng súng nhả đạn, viên đạn thứ 2 thực chất là một BẢN SAO y hệt của viên đạn thứ 1. Hoặc khi bạn clone một template báo cáo.

Nếu chúng ta không định nghĩa cách copy, C++ sẽ tự động copy "từng byte một" (Shallow Copy). Điều này sẽ dẫn đến những lỗi sập bộ nhớ cực kỳ thảm khốc nếu Object của bạn có sử dụng con trỏ hoặc mảng động.

Làm sao để dạy C++ cách nhân bản một Object an toàn? Hẹn gặp lại anh em ở Bài 16: Phương thức khởi tạo sao chép (Copy Constructor) - Nghệ thuật nhân bản thực thể!. Đừng quên Upvote để tiếp thêm năng lượng cho series nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí