Quản lý kế thừa: final
và delete
MQL5 cho phép bạn áp đặt một số hạn chế đối với việc kế thừa của các lớp và cấu trúc.
Từ khóa final
Bằng cách sử dụng từ khóa final
được thêm sau tên lớp, nhà phát triển có thể vô hiệu hóa việc kế thừa từ lớp đó. Ví dụ (FinalDelete.mq5
):
class Base
{
};
class Derived final : public Base
{
};
class Concrete : public Derived // LỖI
{
};
2
3
4
5
6
7
8
9
10
11
Trình biên dịch sẽ báo lỗi "không thể kế thừa từ Derived
vì nó đã được khai báo là final
".
Thật không may, không có sự đồng thuận về lợi ích và kịch bản sử dụng của hạn chế này. Từ khóa này cho phép người dùng lớp biết rằng tác giả của nó, vì một lý do nào đó, không khuyến nghị sử dụng nó làm lớp cơ sở (ví dụ, triển khai hiện tại của nó là bản nháp và sẽ thay đổi nhiều, điều này có thể khiến các dự án kế thừa cũ không còn biên dịch được).
Một số người cố gắng khuyến khích thiết kế chương trình theo cách này, trong đó việc bao gồm các đối tượng (tổ hợp) được sử dụng thay vì kế thừa. Việc quá say mê kế thừa thực sự có thể làm tăng sự gắn kết giữa các lớp (tức là ảnh hưởng lẫn nhau), vì tất cả các lớp thừa kế theo một cách nào đó có thể thay đổi dữ liệu hoặc phương thức của lớp cha (đặc biệt, bằng cách định nghĩa lại các hàm ảo). Kết quả là, độ phức tạp của logic hoạt động của chương trình và khả năng xảy ra các tác dụng phụ không lường trước được tăng lên.
Một lợi thế bổ sung của việc sử dụng final
có thể là tối ưu hóa mã bởi trình biên dịch: đối với các con trỏ của các kiểu "final", nó có thể thay thế việc phân phối động của các hàm ảo bằng phân phối tĩnh.
Từ khóa delete
Từ khóa delete
có thể được chỉ định trong tiêu đề của một phương thức để khiến nó không thể truy cập trong lớp hiện tại và các lớp con của nó. Các phương thức ảo của lớp cha không thể bị xóa theo cách này (điều này sẽ vi phạm "hợp đồng" của lớp, tức là các lớp thừa kế sẽ không còn "là" ("is a") đại diện của cùng một loại).
class Base
{
public:
void method() { Print(__FUNCSIG__); }
};
class Derived : public Base
{
public:
void method() = delete;
};
void OnStart()
{
Base *b;
Derived d;
b = &d;
b.method();
// LỖI:
// cố gắng tham chiếu đến hàm đã bị xóa 'void Derived::method()'
// hàm 'void Derived::method()' đã được xóa rõ ràng
d.method();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Việc cố gắng gọi nó sẽ dẫn đến lỗi biên dịch.
Chúng ta đã thấy một lỗi tương tự trong phần Ép kiểu loại đối tượng vì trình biên dịch có một số thông minh để cũng "xóa" các phương thức trong một số điều kiện nhất định.
Nên đánh dấu là đã xóa các phương thức sau mà trình biên dịch cung cấp triển khai ngầm định:
- Hàm tạo mặc định:
Class(void) = delete
; - Hàm tạo sao chép:
Class(const Class &object) = delete
; - Toán tử sao chép/gán:
void operator=(const Class &object) = delete
.
Nếu bạn cần bất kỳ phương thức nào trong số này, bạn phải định nghĩa chúng một cách rõ ràng. Nếu không, việc từ bỏ triển khai ngầm định được coi là thực hành tốt. Vấn đề là triển khai ngầm định khá đơn giản và có thể gây ra các vấn đề khó xác định vị trí, đặc biệt là khi ép kiểu các loại đối tượng.