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: helloCá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ệtTrì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.
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ênFormatterđể đị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
Formatterchí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: truehoặcBoolean: 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
formatPairnhậ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
formatPairchoconst char*, đặt mỗi chuỗi trong dấu ngoặc kép:Pair: ["<first>", "<second>"]- Một phương thức
main.cpp: Đọc bốn đầu vào, mỗi đầu vào trên một dòng riêng:- Một số nguyên
- Một số thực double
- Một giá trị boolean dưới dạng chuỗi (
truehoặcfalse) - 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
Formatterphù hợp và gọiformat()cho từng kiểu:- Định dạng số nguyên bằng
Formatter<int> - Định dạng số thực double bằng
Formatter<double> - Định dạng giá trị boolean bằng
Formatter<bool>(trước tiên, chuyển chuỗi đầu vào thành bool) - Đị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:
- Gọi
formatPairvới hai số nguyên: 10 và 20 - Gọi
formatPairvớ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;
}
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
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online