Mẫu lớp & CTAD
Xây dựng cấu trúc dữ liệu chung với các mẫu lớp. Hiểu định nghĩa thành viên, đối số mặc định và cách CTAD của C++17 cho phép bạn bỏ qua đối số mẫu.
Mẫu lớp học
Mẫu lớp xác định một họ các lớp được tham số hóa theo một hoặc nhiều loại (hoặc các giá trị không phải loại). Thư viện chuẩn được xây dựng trên các mẫu lớp:std::vector,std::map,std::optional,std::shared_ptr. Khi bạn viếtstd::vector, trình biên dịch sẽ khởi tạo toàn bộ lớp bằngT = int, tạo ra một kiểu cụ thể.
Các mẫu lớp tuân theo các quy tắc giống như các mẫu hàm: chúng phải được xác định trong các tiêu đề và mỗi bộ đối số mẫu duy nhất sẽ tạo ra một loại riêng biệt.StackvàStacklà loại hoàn toàn không liên quan — chúng không có mối quan hệ thừa kế.
Một ngăn xếp chung
Đây là một ngăn xếp chung đơn giản. Lưu ý cách các hàm thành viên được xác định bên ngoài nội dung lớp yêu cầu lặp lại danh sách tham số mẫu.
#include <vector>
#include <stdexcept>
#include <iostream>
template <typename T>
class Stack {
private:
std::vector<T> elements_;
public:
void push(const T& value);
void pop();
const T& top() const;
bool empty() const { return elements_.empty(); }
std::size_t size() const { return elements_.size(); }
};
// Member definitions outside the class require the template header
template <typename T>
void Stack<T>::push(const T& value) {
elements_.push_back(value);
}
template <typename T>
void Stack<T>::pop() {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elements_.pop_back();
}
template <typename T>
const T& Stack<T>::top() const {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elements_.back();
}
int main() {
Stack<int> intStack;
intStack.push(42);
intStack.push(17);
std::cout << intStack.top() << '\n'; // 17
intStack.pop();
std::cout << intStack.top() << '\n'; // 42
}Nhiều tham số & mặc định
Mẫu lớp có thể có nhiều tham số mẫu và đối số mẫu mặc định, giống như tham số hàm có thể có giá trị mặc định. Đối số mặc định được giải quyết từ trái sang phải.
#include <vector>
#include <deque>
#include <iostream>
// Container defaults to std::deque<T> — matches std::stack's design
template <typename T, typename Container = std::deque<T>>
class Stack {
private:
Container elements_;
public:
void push(const T& value) { elements_.push_back(value); }
void pop() { elements_.pop_back(); }
const T& top() const { return elements_.back(); }
bool empty() const { return elements_.empty(); }
};
int main() {
Stack<int> s1; // uses deque<int>
Stack<int, std::vector<int>> s2; // uses vector<int>
s1.push(10);
s2.push(20);
std::cout << s1.top() << '\n'; // 10
std::cout << s2.top() << '\n'; // 20
}Khấu trừ đối số mẫu lớp (CTAD)
Trước C++17, bạn luôn phải chỉ định các đối số mẫu khi xây dựng các phiên bản mẫu lớp:std::pair. CTAD (Khấu trừ đối số mẫu lớp), được giới thiệu trong C++17, cho phép trình biên dịch suy ra các đối số mẫu lớp từ các đối số của hàm tạo, giống như khấu trừ đối số mẫu hàm.
Với CTAD:std::pair p(1, 3.14);- trình biên dịch suy rastd::pair. Tính năng này hoạt động vớistd::vector,std::tuple,std::optional,std::array(C++20) và các mẫu lớp của riêng bạn.
CTAD sử dụng hướng dẫn suy luận — ngầm định (được tạo từ hàm tạo) hoặc rõ ràng (do người dùng xác định). Trình biên dịch thử từng hướng dẫn và chọn kết quả phù hợp nhất.
CTAD & Hướng dẫn khấu trừ
Hướng dẫn suy luận do người dùng xác định cho trình biên dịch biết cách ánh xạ các đối số của hàm tạo với các đối số mẫu. Chúng đặc biệt hữu ích khi kiểu đối số hàm tạo không tương ứng trực tiếp với tham số mẫu.
#include <iostream>
#include <cstddef>
template <typename T, std::size_t N>
struct Array {
T data[N];
};
// Without a deduction guide, CTAD cannot deduce T and N.
// This explicit deduction guide maps brace-init with N elements
// of the same type T to Array<T, N>.
template <typename First, typename... Rest>
Array(First, Rest...) -> Array<First, 1 + sizeof...(Rest)>;
// Standard library example: std::vector CTAD from iterators
#include <vector>
#include <list>
int main() {
// CTAD with our custom deduction guide
Array arr{1, 2, 3, 4, 5}; // deduces Array<int, 5>
std::cout << arr.data[2] << '\n'; // 3
// Standard library CTAD examples (C++17)
std::pair p(42, 3.14); // pair<int, double>
std::vector v{1, 2, 3}; // vector<int>
std::tuple t(1, 2.0, 'a'); // tuple<int, double, char>
// CTAD from iterator range
std::list<int> lst{10, 20, 30};
std::vector v2(lst.begin(), lst.end()); // vector<int>
}Mẫu thành viên
Bản thân các hàm thành viên của mẫu lớp có thể là mẫu, thêm các tham số mẫu bổ sung. Đây được gọi là mẫu thành viên. Một trường hợp sử dụng phổ biến là cho phép gán hoặc xây dựng từ một phiên bản có liên quan nhưng khác nhau.
Ví dụ: bạn có thể muốn gánStacktừStack— nhưng vì đây là các loại không liên quan nên việc gán bản sao mặc định sẽ không hoạt động. Toán tử gán mẫu thành viên giải quyết vấn đề này:
template <typename T>
class Stack {
public:
template <typename U>
Stack& operator=(const Stack<U>& other);
};Inside this member template, U may differ from T. The body can copy elements from other as long as U is convertible to T. This pattern is used extensively in the standard library — std::shared_ptr converts to std::shared_ptr thông qua trình tạo mẫu thành viên.
Khi bạn chuyên môn hóa một mẫu lớp, chuyên môn đó là lớp hoàn toàn mới — nó không kế thừa bất kỳ thành viên nào từ mẫu chính. Bạn phải xác định lại mọi hàm thành viên và thành viên dữ liệu. Việc quên xác định lại thành viên trong chuyên môn sẽ dẫn đến lỗi biên dịch khi sử dụng thành viên đó. Ngoài ra, chuyên môn hóa một phần chỉ có sẵn cho các mẫu lớp, không có sẵn cho các mẫu hàm. Chúng tôi đề cập đến chuyên môn một cách chi tiết trong chương tiếp theo.
- Mẫu lớp xác định họ lớp - mỗi bộ đối số duy nhất tạo ra một kiểu riêng biệt
- Các hàm thành viên được xác định bên ngoài lớp phải lặp lại tiêu đề
templatevà sử dụngClassName:: - Đối số mẫu mặc định hoạt động từ trái sang phải, giống như đối số hàm mặc định
- CTAD (C++17) cho phép trình biên dịch suy ra các đối số mẫu lớp từ các đối số của hàm tạo
- Hướng dẫn suy luận rõ ràng ánh xạ các mẫu đối số của hàm tạo thành các đối số mẫu
- Các mẫu thành viên cho phép thực hiện các thao tác trên các phiên bản khác nhau của cùng một mẫu lớp
Câu đố — Kiểm tra kiến thức
(15 XP)1. Với`template <typename T, typename Container = std::deque<T>> class Stack`, loại`Stack<int>`là gì?
2. CTAD (C++17) là gì?
3. Khi bạn chuyên môn hóa một mẫu lớp, điều gì xảy ra với các thành viên của mẫu chính?