Cowboy Coder

To code like a Cowboy!

[C++ Cơ bản] Phần 27: Template. Thư viện template chuẩn của C++.

Phần trước: [C++ Cơ bản] Phần 26: Bài thực hành - Game đố vui

Giả sử ta muốn tạo ra một hàm lấy max của hai số nguyên, ta sẽ viết như sau

int Max(int x, int y)
{
    return (x > y? x : y);
}

Cũng trong cùng một chương trình đó, ta muốn lấy max của hai string, ta sẽ phải viết thêm một hàm mới

string Max(string x, string y) // Overload
{
    return (x > y? x : y);
}

Nếu như chỉ tới đây thôi thì không sao. Đằng này chương trình lại còn cần phải lấy max của hai giá trị kiểu char, rồi double, của giá trị kiểu class mà ta thiết kế ra… Chả lẽ lại viết lại hết tất cả các hàm có cùng một nội dung lặp đi lặp lại? Template ra đời nhằm mục giải quyết các vấn đề như vậy.

Template và Generic programming

Generic programming là khả năng cho phép lập trình viên có thể viết chương trình có khả năng thích nghi với nhiều kiểu dữ liệu, thay vì một kiểu dữ liệu cố định. Kiểu dữ liệu được sử dụng sẽ được cung cấp khi chương trình gọi tới hàm hoặc class cài đặt generic. Trong C++, ta có thể áp dụng generic thông qua từ khóa template (kiểu mẫu) và các tham số template. Tham số template là một kiểu tham số đặc biệt, có khả năng truyền kiểu dữ liệu/class vào để dùng trong hàm hoặc class generic.

Để áp dụng generic, ta chèn thêm nội dung sau vào trước hàm hoặc class cần cài đặt:

template </* danh sách tham số */>

Trong đó danh sách tham số được liệt kê giống như tham số của hàm, trừ một điểm: kiểu dữ liệu có thể được truyền vào làm tham số, nếu kiểu dữ liệu của tham số này là typename hoặc class. Hai từ khóa này có giá trị như nhau trong template, nên bạn có thể sử dụng cái nào cũng được.

Sau khi khai báo tham số template, ta có thể sử dụng tên của các tham số trong hàm hoặc class mục tiêu.

Ví dụ với hàm max ở trên, ta muốn áp dụng generic để có một hàm max đa năng với mọi kiểu dữ liệu. Ta sẽ làm như sau:

template <typename T> T Max(T x, T y)
{
    return (x > y? x : y);
}

Về sau chỉ cần gọi hàm Max() với kiểu dữ liệu làm tham số template T, chương trình sẽ tự động thay T bằng kiểu dữ liệu được cung cấp trong ngoặc <>, và chạy chương trình bình thường.

#include <iostream>

using namespace std;

template <typename T> T Max(T x, T y)
{
    return (x > y? x : y);
}

int main()
{
    cout << Max<int>(2, 3) << ' ' << Max<string>("abc", "adc");
    return 0;
}

Output

3 adc

Ví dụ khi áp dụng với class: Một class với biến value được cung cấp kiểu dữ liệu về sau, thông qua template.

#include <iostream>

using namespace std;

template <typename T> class Container
{
    public:
        T value;
};

int main()
{
    Container<string> stringContainer;
    stringContainer.value = "Hello world!";
    Container<int> intContainer;
    intContainer.value = 100;
    cout << stringContainer.value << ' ' << intContainer.value;
    return 0;
}

Output

Hello world! 100

Chú ý rằng trong trường hợp với hàm Max() ở trên, nếu như kiểu dữ liệu được cung cấp vào không hỗ trợ toán tử > (ví dụ như một class không được overload toán tử này), hàm sẽ không thể hoạt động và chương trình sẽ báo lỗi khi dịch.

Ngoài kiểu dữ liệu ra, ta có thể truyền thông tin khác vào template được không?

Hoàn toàn có thể! Ta chỉ cần thay typename hoặc class bằng kiểu dữ liệu bình thường là được. Chú ý tham số được cung cấp vào phải hoàn toàn xác định vào thời điểm dịch chương trình (có thể là các giá trị được viết thẳng như 100, 'a', "abc", hoặc các biến được đánh dấu là hằng số bằng từ khóa const). Nếu không, chương trình sẽ báo lỗi khi dịch.

Ví dụ áp dụng: Class sau sẽ có một mảng có kiểu dữ liệu và kích cỡ được cung cấp về sau qua template.

template <typename T, int size> class Example
{
    public:
        T arr[size];
};

Example<int, 100> a;

Thư viện template chuẩn STL của C++

Như các bạn đã thấy, template cho phép chúng ta tạo ra các đoạn code đa năng, sử dụng được với nhiều kiểu dữ liệu và trong nhiều trường hợp khác nhau. Bản thân ngôn ngữ C++ cũng cung cấp cho chúng ta hệ thống thư viện STL - Standard Template Library - với rất nhiều template hàm và cấu trúc dữ liệu mạnh mẽ, sẵn sàng đáp ứng các yêu cầu của dự án lập trình.

STL rất rộng lớn, để có thể bao quát được hết các thư viện sẽ mất nhiều thời gian và đòi hỏi phải áp dụng nhiều trong thực tế để có thể thuần thục được. Các bạn có thể nghiên cứu thêm về hệ thống thư viện STL trong tài liệu C++ STL for newbies của tác giả Điêu Xuân Mạnh.

Phần sau: [C++ Cơ bản] Phần 28: Áp dụng C++ vào lập trình thực tế (phần cuối)