Menu
CoddyTech

Chuyên biệt hóa mẫu

Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 66 trên 104.

Đôi khi, mẫu tổng quát không hoạt động tốt với mọi kiểu dữ liệu. Ví dụ, so sánh chuỗi kiểu C bằng > sẽ so sánh địa chỉ con trỏ, không phải văn bản thực tế. Chuyên biệt hóa mẫu cho phép bạn cung cấp một cách triển khai riêng cho các kiểu dữ liệu cụ thể, đồng thời vẫn giữ phiên bản tổng quát cho mọi kiểu dữ liệu khác.

Chuyên biệt hóa toàn phần thay thế toàn bộ mẫu cho một kiểu cụ thể. Bạn khai báo bằng danh sách tham số mẫu rỗng và chỉ định kiểu cụ thể:

// Mẫu chính
template <typename T>
class Printer {
public:
    void print(T value) {
        std::cout << value << std::endl;
    }
};

// Chuyên biệt hóa hoàn toàn cho const char*
template <>
class Printer<const char*> {
public:
    void print(const char* value) {
        std::cout << "String: " << value << std::endl;
    }
};

Printer<int> intPrinter;
intPrinter.print(42);           // Đầu ra: 42

Printer<const char*> strPrinter;
strPrinter.print("hello");      // Đầu ra: String: hello

Các khuôn mẫu hàm cũng có thể được chuyên biệt hóa:

template <typename T>
bool isEqual(T a, T b) {
    return a == b;
}

template <>
bool isEqual<const char*>(const char* a, const char* b) {
    return std::strcmp(a, b) == 0;
}

isEqual(5, 5);              // Dùng phiên bản tổng quát
isEqual("hi", "hi");        // Dùng phiên bản chuyên biệt

Trình biên dịch luôn ưu tiên kết quả khớp cụ thể nhất: nếu có một phiên bản chuyên biệt hóa (specialization) cho đúng kiểu đang dùng, trình biên dịch sẽ chọn phiên bản đó thay vì mẫu tổng quát.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống định dạng có xét đến kiểu dữ liệu để minh họa cách chuyên biệt hóa mẫu cho phép bạn tùy chỉnh hành vi theo từng kiểu cụ thể, đồng thời vẫn duy trì cách xử lý dự phòng tổng quát cho mọi kiểu dữ liệu khác.

Bạn sẽ tạo hai tệp để sắp xếp code mẫu của mình:

  • Formatter.h: Định nghĩa một mẫu lớp có tên Formatter để định dạng các giá trị hiển thị. Mẫu tổng quát của bạn cần hoạt động với mọi kiểu dữ liệu, nhưng bạn cũng sẽ tạo các phiên bản chuyên biệt cho những kiểu cần cách xử lý riêng.

    Mẫu Formatter chính cần có:

    • Một phương thức format() nhận một giá trị kiểu T và in ra: Value: <value>

    Tạo một phiên bản chuyên biệt toàn phần cho bool để in ra văn bản dễ hiểu thay vì 1 hoặc 0:

    • Phương thức format() cần in ra: Boolean: true hoặc Boolean: false

    Tạo một phiên bản chuyên biệt toàn phần cho const char* để đặt dấu ngoặc kép quanh các chuỗi:

    • Phương thức format() cần in ra: String: "<value>"

    Đồng thời, tạo một mẫu hàm có tên formatPair nhận hai giá trị cùng kiểu và in chúng cùng nhau theo dạng: Pair: [<first>, <second>]

    Tạo một phiên bản chuyên biệt của formatPair cho const char*, đặt mỗi chuỗi trong dấu ngoặc kép: Pair: ["<first>", "<second>"]

  • main.cpp: Đọc bốn đầu vào, mỗi đầu vào trên một dòng riêng:
    1. Một số nguyên
    2. Một số thực double
    3. Một giá trị boolean dưới dạng chuỗi (true hoặc false)
    4. Một giá trị chuỗi

    Minh họa các bộ định dạng bằng cách tạo những đối tượng Formatter phù hợp và gọi format() cho từng kiểu:

    1. Định dạng số nguyên bằng Formatter<int>
    2. Định dạng số thực double bằng Formatter<double>
    3. Định dạng giá trị boolean bằng Formatter<bool> (trước tiên, chuyển chuỗi đầu vào thành bool)
    4. Định dạng chuỗi bằng Formatter<const char*>

    Sau đó, minh họa việc chuyên biệt hóa mẫu hàm:

    1. Gọi formatPair với hai số nguyên: 10 và 20
    2. Gọi formatPair với hai chuỗi C: "hello" và "world"

Ví dụ, với các đầu vào 42, 3.14, true và Hello:

Value: 42
Value: 3.14
Boolean: true
String: "Hello"
Pair: [10, 20]
Pair: ["hello", "world"]

Hãy chú ý cách mẫu tổng quát xử lý số nguyên và số thực double giống nhau, trong khi các phiên bản chuyên biệt cho bool và const char* cung cấp cách định dạng riêng. Trình biên dịch tự động chọn kiểu khớp cụ thể nhất cho mỗi kiểu dữ liệu bạn sử dụng.

Tự làm thử

#include <iostream>
#include <string>
#include "Formatter.h"

using namespace std;

int main() {
    // Đọc dữ liệu đầu vào
    int intVal;
    double doubleVal;
    string boolStr;
    string strVal;
    
    cin >> intVal;
    cin >> doubleVal;
    cin >> boolStr;
    cin >> strVal;
    
    // Chuyển chuỗi thành bool
    bool boolVal = (boolStr == "true");
    
    // TODO: Tạo Formatter<int> và định dạng số nguyên
    
    // TODO: Tạo Formatter<double> và định dạng số thực
    
    // TODO: Tạo Formatter<bool> và định dạng giá trị logic
    
    // TODO: Tạo Formatter<const char*> và định dạng chuỗi
    // Gợi ý: Dùng strVal.c_str() để lấy const char*
    
    // TODO: Gọi formatPair với hai số nguyên: 10 và 20
    
    // TODO: Gọi formatPair với hai chuỗi C: "hello" và "world"
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C++ online