Giới thiệu về Template trong C++

Lập trình tổng quát (Generic Programming)

Viết mã nguồn không phụ thuộc vào kiểu dữ liệu cụ thể là một phương pháp tái sử dụng mã nguồn hiệu quả, và template chính là nền tảng để thực hiện lập trình tổng quát trong C++.

Xét ví dụ về hàm hoán đổi giá trị. Trong C, chúng ta cần tạo các hàm có tên khác nhau để xử lý từng kiểu dữ liệu. Sau khi học về function overloading, chúng ta có thể viết các hàm cùng tên nhưng với tham số khác nhau. Tuy nhiên, với mỗi kiểu dữ liệu mới, chúng ta vẫn phải viết lại hàm. Template giúp giải quyết vấn đề này bằng cách định nghĩa một "khuôn mẫu" chung, từ đó compiler sẽ tự động sinh ra các phiên bản hàm phù hợp cho từng kiểu dữ liệu.

// Ngôn ngữ C
void HoanDoi_int(int* a, int* b);
void HoanDoi_double(double* a, double* b);

// Function overloading
void HoanDoi(int& a, int& b);
void HoanDoi(double& a, double& b);

// Template
template<typename LoaiDuLieu>
void HoanDoi(LoaiDuLieu& a, LoaiDuLieu& b)
{
    LoaiDuLieu tam = a;
    a = b;
    b = tam;
}
// Compiler sẽ sinh ra các phiên bản tương ứng
HoanDoi<float>(1.0f, 2.0f); // Gọi phiên bản float
HoanDoi<int>(1, 2);         // Gọi phiên bản int

Function Template (Khuôn mẫu hàm)

Khái niệm

Function template đại diện cho một họ các hàm, không phụ thuộc vào kiểu dữ liệu cụ thể. Khi được sử dụng, template sẽ được instance hóa để tạo ra phiên bản hàm phù hợp với kiểu dữ liệu thực tế.

Template giống như một bản thiết kế, không phải là hàm thực sự. Nó là "khuôn mẫu" để compiler sinh ra các hàm cụ thể, giúp tiết kiệm công sức của lập trình viên trong việc viết các hàm có logic lặp đi lặp lại.

Trong giai đoạn biên dịch, compiler sẽ phân tích kiểu dữ liệu của tham số truyền vào và sinh ra phiên bản hàm tương ứng để gọi.

Cú pháp

template<typename T1, typename T2,.....,typename Tn>

Kiểu_trả_về Tên_hàm(Danh_sách_tham_số) {}

template<typename LoaiDuLieu>
void HoanDoi(LoaiDuLieu& a, LoaiDuLieu& b)
{
    LoaiDuLieu tam = a;
    a = b;
    b = tam;
}

Từ khóa typename được dùng để khai báo tham số template, ngoài ra có thể sử dụng từ khóa class với ý nghĩa tương tự.

Instance hóa (Khởi tạo)

1. Instance hóa ngầm định - Để compiler tự suy luận kiểu dữ liệu từ tham số truyền vào

// Sử dụng hàm HoanDoi định nghĩa ở trên
int main()
{
    int x1 = 10, x2 = 20;
    double y1 = 10.0, y2 = 20.0;
    HoanDoi(x1, x2);
    HoanDoi(y1, y2);
    // HoanDoi(x1, y2); // Lỗi vì compiler không thể xác định LoaiDuLieu là int hay double
    // Có thể sử dụng cast hoặc instance hóa hiển thị
    HoanDoi(x1, (int)y1);
    HoanDoi(y1, (double)x2);
    
    return 0;
}

2. Instance hóa hiển thị - Chỉ định rõ kiểu dữ liệu trong cặp ngoặc sau tên hàm

int main()
{
    int a = 10;
    double b = 20.0;
    HoanDoi<int>(a, b);
    
    return 0;
}

Quy tắc ưu tiên khớp

Một hàm thông thường có thể cùng tên với một function template. Khi gọi hàm, nếu có thể khớp với hàm thông thường, compiler sẽ ưu tiên gọi hàm đó thay vì instance hóa template.

void HoanDoi(int& trai, int& phai)
{
    int tam = trai;
    trai = phai;
    phai = tam;
}

template<typename LoaiDuLieu>
void HoanDoi(LoaiDuLieu& trai, LoaiDuLieu& phai)
{
    LoaiDuLieu tam = trai;
    trai = phai;
    phai = tam;
}

int main()
{
    int m = 0, n = 10;
    HoanDoi(m, n);        // Khớp với hàm thông thường, không cần instance hóa template
    HoanDoi<int>(m, n);   // Gọi phiên bản được instance hóa từ template

    return 0;
}

Lưu ý: Template function không hỗ trợ chuyển đổi kiểu tự động, trong khi hàm thông thường cho phép chuyển đổi kiểu ngầm định.

Class Template (Khuôn mẫu lớp)

Cú pháp

template<class T1, class T2,..., class Tn>
class TenLop
{
  // Định nghĩa các thành viên của lớp  
};

// Ví dụ: Danh sách động
// MangKhongGian không phải là một lớp cụ thể, mà là khuôn mẫu để compiler sinh ra lớp
template<class KieuDuLieu>
class MangKhongGian
{
public:
    MangKhongGian(size_t dungLuong = 10)
        :_duLieu(new KieuDuLieu[dungLuong])
        , _soPhanTu(0)
        , _dungLuong(dungLuong)
    {}
    
    // Hàm hủy khai báo trong lớp, định nghĩa bên ngoài
    ~MangKhongGian();
    
    void ThemPhanTu(const KieuDuLieu& giaTri)
    {
        // ... xử lý thêm phần tử
    }
    
private:
    KieuDuLieu* _duLieu;
    size_t _soPhanTu;
    size_t _dungLuong;
}

// Định nghĩa hàm hủy bên ngoài lớp
template<class KieuDuLieu>
MangKhongGian<KieuDuLieu>::~MangKhongGian()
{
    if (_duLieu)
        delete[] _duLieu;
    _soPhanTu = _dungLuong = 0;
}

Instance hóa

// MangKhongGian là tên lớp, MangKhongGian<int> mới là kiểu dữ liệu hoàn chỉnh
MangKhongGian<int> daySo;
MangKhongGian<double> dayThuc;

Thẻ: C++ templates generic-programming STL object-oriented-programming

Đăng vào ngày 18 tháng 6 lúc 16:50