+3

[C++ OOP Thực Chiến] Bài 19: Phương thức tĩnh (Static Member Functions) - Quyền năng gọi hàm không cần Object

Chào anh em! Ở [Bài 18], chúng ta đã biết cách tạo ra một static data member (Biến tĩnh) để làm "Sổ cái chung" lưu trữ dữ liệu toàn cục của Class (ví dụ: đếm tổng số Giao dịch).

Nhưng chúng ta đã kết thúc bằng một câu hỏi rất hóc búa: Làm sao để đọc được cái "Sổ cái" đó khi hệ thống vừa khởi động, tức là khi chưa có bất kỳ một Object nào được tạo ra? Và vì "Sổ cái" được đặt ở chế độ private để bảo mật (Encapsulation), chúng ta không thể gọi thẳng Transaction::transactionCounter được. Giải pháp duy nhất và thanh lịch nhất của một Kỹ sư C++ chính là: Static Member Functions (Phương thức tĩnh).

1. Phương thức tĩnh là gì?

Nếu Biến tĩnh là "Tài sản chung", thì Phương thức tĩnh là "Hành động chung". Nó là một hàm thuộc về toàn bộ Class, chứ không thuộc về một Object cụ thể nào cả.

Vì nó không thuộc về Object nào, bạn không cần tạo Object vẫn có thể gọi được nó!

Cú pháp gọi hàm:

// Gọi thẳng tên Class, kèm theo Toán tử phân giải ::
ClassName::StaticFunctionName();

2. Hai "Quy tắc thép" định hình đẳng cấp

Khi đi phỏng vấn vị trí Backend/C++, người ta rất hay hỏi xoáy vào Phương thức tĩnh. Để không bị "ăn hành", anh em chỉ cần nhớ kỹ 2 quy tắc sinh tử này:

Quy tắc 1: Phương thức tĩnh CHỈ ĐƯỢC PHÉP chạm vào Biến tĩnh. Vì phương thức tĩnh có thể được gọi khi chưa có Object nào tồn tại, nên nó hoàn toàn mù tịt về các biến thông thường (non-static). Nếu bạn cố tình dùng một hàm static để in ra name hay balance của một Object, trình biên dịch sẽ vả bạn sấp mặt vì: "Tao biết mày đang hỏi name của thằng nào đâu mà in?".

Quy tắc 2: Tuyệt đối không có con trỏ this. Như đã học ở Bài 10, this là con trỏ đại diện cho Object đang gọi hàm. Vì Phương thức tĩnh không cần Object để gọi, nên this hoàn toàn không tồn tại bên trong hàm này.

3. Code Demo: Xây dựng hệ thống Connection Pool

Hãy cùng ứng dụng Phương thức tĩnh vào một bài toán Backend kinh điển: Quản lý số lượng kết nối đến Database (Connection Pool). Chúng ta cần giới hạn số lượng kết nối tối đa để server không bị sập.

#include <iostream>
#include <string>

using namespace std;

class DBConnection {
private:
    string targetDB;
    
    // BIẾN TĨNH: Lưu số lượng kết nối đang mở
    static int activeConnections;
    // BIẾN TĨNH: Giới hạn tối đa của server
    static const int MAX_CONNECTIONS = 3; 

public:
    DBConnection(string db) {
        if (activeConnections < MAX_CONNECTIONS) {
            targetDB = db;
            activeConnections++;
            cout << "[SUCCESS] Da ket noi den " << targetDB 
                 << ". (Tong: " << activeConnections << "/" << MAX_CONNECTIONS << ")\n";
        } else {
            cout << "[FAILED] Server qua tai! Khong the ket noi den " << db << "\n";
        }
    }

    ~DBConnection() {
        if (!targetDB.empty()) { // Nếu kết nối thành công thì mới giảm
            activeConnections--;
            cout << "[CLOSED] Ngat ket noi " << targetDB 
                 << ". (Tong: " << activeConnections << "/" << MAX_CONNECTIONS << ")\n";
        }
    }

    // PHƯƠNG THỨC TĨNH: Có thể gọi mọi lúc mọi nơi
    static void checkServerLoad() {
        cout << "--- TRANG THAI SERVER ---\n";
        cout << "Ket noi dang hoat dong: " << activeConnections << "\n";
        cout << "Khả dụng: " << (MAX_CONNECTIONS - activeConnections) << "\n";
        cout << "-------------------------\n";
        
        // cout << targetDB; // LỖI COMPILER NGAY: Không thể truy cập biến non-static!
    }
};

// Định nghĩa biến tĩnh bên ngoài Class
int DBConnection::activeConnections = 0;

int main() {
    // 1. Gọi hàm Tĩnh khi CHƯA CÓ BẤT KỲ KẾT NỐI NÀO
    DBConnection::checkServerLoad(); 

    cout << "\n[Hệ thống bắt đầu request...]\n\n";

    // 2. Tạo các kết nối (Objects)
    DBConnection conn1("User_DB");
    DBConnection conn2("Payment_DB");
    DBConnection conn3("Log_DB");

    // Thử tạo kết nối thứ 4 (Sẽ bị chặn)
    DBConnection conn4("Analytics_DB"); 

    cout << "\n";
    // 3. Kiểm tra lại tải server giữa phiên làm việc
    DBConnection::checkServerLoad();

    return 0;
}

Nhận xét: Hàm checkServerLoad() hoạt động cực kỳ hoàn hảo như một bảng điều khiển trung tâm (Dashboard). Bạn có thể đặt nó ở bất kỳ đâu trong source code để monitor (giám sát) hệ thống mà không cần phải nhọc công truyền Object qua lại.

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

Chúc mừng anh em! Với việc làm chủ được Class, Object, Constructor, Destructor, Con trỏ this, Tham chiếu &, và cụm từ khóa static, các bạn đã chính thức nắm trong tay toàn bộ "vũ khí" nền tảng của Lập trình Hướng đối tượng trong C++.

Đã đến lúc chúng ta gập sách lý thuyết lại và bắt tay vào THỰC CHIẾN MẠNH.

Giả sử bạn đang viết phần mềm Toán học. C++ cho phép bạn cộng 2 số nguyên rất dễ: int c = a + b;. Nhưng nếu bạn tạo ra một Class PhanSo (Phân số), làm sao để C++ hiểu được cú pháp PhanSo ps3 = ps1 + ps2; thay vì phải gọi hàm lằng nhằng ps1.cong(ps2)?

Để dọn đường cho kỹ thuật "Nạp chồng toán tử" (Operator Overloading) bá đạo nhất của C++, chúng ta sẽ cùng nhau xây dựng nền móng kiến trúc qua bài thực hành lớn.

Hẹn gặp lại anh em ở Bài 20: Bài tập thực chiến - Xây dựng hệ thống Phân số (Phần 1). Nhớ chuẩn bị sẵn ly cà phê và mở IDE lên 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í