Nguyên tắc hoạt động chung của template
Hãy nhớ lại nạp chồng hàm. Nó bao gồm việc định nghĩa nhiều phiên bản của một hàm với các tham số khác nhau, bao gồm cả trường hợp số lượng tham số giống nhau nhưng kiểu của chúng khác nhau. Thường thì thuật toán của các hàm như vậy là giống nhau đối với các tham số thuộc các kiểu khác nhau. Ví dụ, MQL5 có hàm tích hợp sẵn MathMax
trả về giá trị lớn nhất trong hai giá trị được truyền vào nó:
double MathMax(double value1, double value2);
Mặc dù nguyên mẫu chỉ được cung cấp cho kiểu double
, nhưng thực tế hàm này có khả năng hoạt động với các cặp đối số thuộc các kiểu số khác, chẳng hạn như int
hoặc datetime
. Nói cách khác, hàm này là một nhân nạp chồng cho các kiểu số tích hợp sẵn. Nếu chúng ta muốn đạt được hiệu ứng tương tự trong mã nguồn của mình, chúng ta sẽ phải nạp chồng hàm bằng cách sao chép nó với các tham số khác nhau, như sau:
double Max(double value1, double value2)
{
return value1 > value2 ? value1 : value2;
}
int Max(int value1, int value2)
{
return value1 > value2 ? value1 : value2;
}
datetime Max(datetime value1, datetime value2)
{
return value1 > value2 ? value1 : value2;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Tất cả các triển khai (phần thân hàm) đều giống nhau. Chỉ có kiểu tham số thay đổi.
Đây là lúc các mẫu trở nên hữu ích. Bằng cách sử dụng chúng, chúng ta có thể mô tả một mẫu của thuật toán với triển khai cần thiết, và trình biên dịch sẽ tự động tạo ra nhiều phiên bản của nó cho các kiểu cụ thể liên quan trong chương trình. Việc tạo này diễn ra ngay trong quá trình biên dịch và không thể nhận thấy đối với lập trình viên (trừ khi có lỗi trong mẫu). Mã nguồn thu được tự động không được chèn vào văn bản chương trình mà được chuyển trực tiếp thành mã nhị phân (tệp ex5).
Trong mẫu, một hoặc nhiều tham số là các ký hiệu chính thức của kiểu, mà tại giai đoạn biên dịch, theo các quy tắc suy ra kiểu đặc biệt, các kiểu thực tế sẽ được chọn từ các kiểu tích hợp sẵn hoặc do người dùng định nghĩa. Ví dụ, hàm Max
có thể được mô tả bằng mẫu sau với tham số kiểu T:
template<typename T>
T Max(T value1, T value2)
{
return value1 > value2 ? value1 : value2;
}
2
3
4
5
Và sau đó - áp dụng nó cho các biến thuộc nhiều kiểu khác nhau (xem TemplatesMax.mq5
):
void OnStart()
{
double d1 = 0, d2 = 1;
datetime t1 = D'2020.01.01', t2 = D'2021.10.10';
Print(Max(d1, d2));
Print(Max(t1, t2));
...
}
2
3
4
5
6
7
8
Trong trường hợp này, trình biên dịch sẽ tự động tạo các biến thể của hàm Max
cho các kiểu double
và datetime
.
Bản thân mẫu không tạo ra mã nguồn. Để làm điều này, bạn cần tạo một phiên bản của mẫu theo một cách nào đó: gọi một hàm mẫu hoặc đề cập đến tên của một lớp mẫu với các kiểu cụ thể để tạo một đối tượng hoặc một lớp dẫn xuất.
Cho đến khi điều này được thực hiện, toàn bộ mẫu sẽ bị trình biên dịch bỏ qua. Ví dụ, chúng ta có thể viết một hàm mẫu giả định sau đây, vốn chứa mã cú pháp không chính xác. Tuy nhiên, việc biên dịch một mô-đun với hàm này sẽ thành công miễn là nó không được gọi ở bất kỳ đâu.
template<typename T>
void function()
{
it's not a comment, but it's not source code either
!%^&*
}
2
3
4
5
6
Đối với mỗi lần sử dụng mẫu, trình biên dịch xác định các kiểu thực tế khớp với các tham số chính thức của mẫu. Dựa trên thông tin này, mã nguồn mẫu được tự động tạo ra cho mỗi tổ hợp tham số duy nhất. Đây là phiên bản.
Vì vậy, trong ví dụ đã cho của hàm Max
, chúng ta đã gọi hàm mẫu hai lần: cho cặp biến kiểu double
, và cho cặp biến kiểu datetime
. Điều này dẫn đến hai phiên bản của hàm Max
với mã nguồn cho các khớp T=double
và T=datetime
. Tất nhiên, nếu cùng một mẫu được gọi ở các phần khác của mã cho cùng các kiểu, sẽ không có phiên bản mới nào được tạo ra. Một phiên bản mới của mẫu chỉ cần thiết nếu mẫu được áp dụng cho một kiểu khác (hoặc tập hợp các kiểu, nếu có nhiều hơn 1 tham số).
Xin lưu ý rằng mẫu Max
có một tham số, và nó đặt kiểu cho cả hai tham số đầu vào của hàm và giá trị trả về của nó cùng một lúc. Nói cách khác, khai báo mẫu có khả năng áp đặt một số hạn chế nhất định đối với các kiểu của các đối số hợp lệ.
Nếu chúng ta gọi Max
trên các biến thuộc các kiểu khác nhau, trình biên dịch sẽ không thể xác định kiểu để tạo phiên bản mẫu và sẽ báo lỗi "tham số mẫu không rõ ràng, phải là double
hoặc datetime
":
Print(Max(d1, t1)); // tham số mẫu không rõ ràng,
// có thể là 'double' hoặc 'datetime'
2
Quá trình khám phá các kiểu thực tế cho các tham số mẫu dựa trên ngữ cảnh sử dụng mẫu được gọi là suy ra kiểu (type deduction). Trong MQL5, suy ra kiểu chỉ khả dụng cho các hàm và phương thức mẫu.
Đối với các lớp, cấu trúc và liên hợp, một cách khác để liên kết kiểu với các tham số mẫu được sử dụng: các kiểu cần thiết được chỉ định rõ ràng trong dấu ngoặc nhọn khi tạo một phiên bản mẫu (nếu có nhiều tham số, thì số lượng kiểu tương ứng được chỉ định dưới dạng danh sách phân tách bằng dấu phẩy). Để biết thêm chi tiết, xem phần Mẫu kiểu đối tượng.
Phương pháp rõ ràng tương tự cũng có thể được áp dụng cho các hàm như một lựa chọn thay thế cho suy ra kiểu tự động.
Ví dụ, chúng ta có thể tạo và gọi một phiên bản của Max
cho kiểu ulong
:
Print(Max<ulong(1000, 10000000));
Trong trường hợp này, nếu không có chỉ định rõ ràng, hàm mẫu sẽ được liên kết với kiểu int
(dựa trên giá trị của các hằng số nguyên).