+1

[C++ OOP Thực Chiến] Bài 28: Nạp chồng toán tử cộng (+) - Cuộc chiến giữa Member và Non-member

Chào anh em! Ở [Bài 27], chúng ta đã khởi động nhẹ nhàng với toán tử 1 ngôi (-ps). Hôm nay, chúng ta sẽ đối mặt với "trùm cuối": Toán tử 2 ngôi (Binary Operator), tiêu biểu nhất là phép cộng +.

Mục tiêu tối thượng của chúng ta là làm sao để C++ chạy mượt mà dòng code này: PhanSo tong = ps1 + ps2;

Đối với phép toán 2 ngôi, bạn có thể triển khai nó theo 2 trường phái: Viết ở TRONG Class (Member) hoặc viết ở NGOÀI Class (Non-member). Cùng xem cuộc chiến giữa 2 lối thiết kế này nhé!

1. Trường phái 1: Góc nhìn từ bên trong (Member Function)

Khi bạn viết hàm operator+ bên trong Class, C++ sẽ ngầm định dịch biểu thức ps1 + ps2 thành một lời gọi hàm rất "ích kỷ": ps1.operator+(ps2)

Tức là: Thằng bên trái (ps1) sẽ làm chủ nhà (đại diện bởi con trỏ this), và nó mời thằng bên phải (ps2) vào nhà làm khách (thông qua tham số truyền vào).

class PhanSo {
private:
    int tuSo, mauSo;
    // ... (Giữ nguyên các hàm rút gọn, in ra như bài trước)

public:
    PhanSo(int tu = 0, int mau = 1) : tuSo(tu), mauSo(mau) {}

    // Nạp chồng phép + (Dạng Member Function)
    // - Tham số 'other' là thằng bên phải dấu +
    // - Con trỏ 'this' là thằng bên trái dấu +
    PhanSo operator+(const PhanSo& other) const {
        // Cộng 2 phân số: a/b + c/d = (a*d + b*c) / (b*d)
        int tuMoi = (this->tuSo * other.mauSo) + (other.tuSo * this->mauSo);
        int mauMoi = this->mauSo * other.mauSo;
        
        return PhanSo(tuMoi, mauMoi); // Tự động gọi Constructor và rút gọn
    }
};

Ưu điểm: Chọc thẳng được vào tuSomauSo của cả 2 đối tượng vì đang đứng bên trong Class. Code cực ngắn!

2. Điểm chết của Member Function (Góc khuất Senior)

Nhìn code ở trên có vẻ hoàn hảo. Nhưng hãy thử đặt vào nghiệp vụ thực tế. Trong toán học, phép cộng có tính giao hoán: A + B = B + A. Và đôi khi, người dùng muốn cộng một phân số với một số nguyên (ví dụ: ps1 + 5).

Giả sử Constructor PhanSo(int tu, int mau=1) của bạn cho phép ép kiểu tự động (không dùng explicit).

Khi bạn gõ: ps1 + 5 -> C++ dịch thành ps1.operator+(PhanSo(5)). Code chạy ngon lành!

Nhưng khi bạn gõ: 5 + ps1 -> C++ dịch thành 5.operator+(ps1).

BÙM! Lỗi Compiler! Tại sao? Vì số 5 là kiểu int nguyên thủy của C++, nó không hề có cái method nào tên là operator+ nhận vào một PhanSo cả! Tính giao hoán của phép cộng đã bị phá nát!

3. Trường phái 2: Đứng từ bên ngoài (Non-member Function)

Để cứu vãn tính giao hoán, Kỹ sư C++ bắt buộc phải đẩy phép cộng ra thành hàm tự do (Non-member). Lúc này, biểu thức a + b sẽ được dịch một cách vô tư, bình đẳng: operator+(a, b)

Cả thằng bên trái và thằng bên phải đều chỉ là 2 tham số ngang hàng. Nếu gõ 5 + ps1, C++ sẽ biến thành operator+(PhanSo(5), ps1). Mọi thứ hoạt động hoàn hảo!

class PhanSo {
private:
    int tuSo, mauSo;
public:
    PhanSo(int tu = 0, int mau = 1) : tuSo(tu), mauSo(mau) {}
    
    // Cánh cửa Getter (Bắt buộc phải có nếu code ở ngoài)
    int getTuSo() const { return tuSo; }
    int getMauSo() const { return mauSo; }
};

// --- HÀM TỰ DO NẰM BÊN NGOÀI CLASS ---
PhanSo operator+(const PhanSo& a, const PhanSo& b) {
    // Vì đứng ở ngoài, không có 'this', và phải dùng Getter!
    int tuMoi = (a.getTuSo() * b.getMauSo()) + (b.getTuSo() * a.getMauSo());
    int mauMoi = a.getMauSo() * b.getMauSo();
    
    return PhanSo(tuMoi, mauMoi);
}

Nhận xét: Lối thiết kế này giúp hệ thống của bạn giữ được tính logic toán học (giao hoán). Tuy nhiên, cái giá phải trả là bạn phải gọi liên tục các hàm getTuSo(), getMauSo(). Nhìn vào công thức (a.getTuSo() * b.getMauSo()) ..., anh em sẽ thấy nó cực kỳ... phèn và rối mắt!

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

Chúng ta đang bị kẹt giữa 2 dòng nước:

  1. Dùng Member Function: Code gọn gàng, truy cập private trực tiếp, nhưng mất tính giao hoán (5 + ps bị lỗi).
  2. Dùng Non-member Function: Đảm bảo tính giao hoán, nhưng code tính toán bị phình to và xấu xí vì phải lạm dụng Getter.

Có cách nào để chúng ta "lấy điểm mạnh của cả hai" không? Một hàm vừa đứng ở ngoài để đảm bảo tính bình đẳng (Non-member), nhưng lại vừa có "đặc quyền" chọc thẳng vào private mà không cần xài Getter?

Câu trả lời đã nằm ngay trong [Bài 22]! Các bạn còn nhớ người bạn được cấp "chìa khóa phòng ngủ" không?

Hẹn gặp lại anh em ở Bài 29: Toán tử + dùng hàm bạn - Chân ái của những hệ thống tính toán! Nhớ thả Upvote để tiếp lửa 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í