Tạo đối tượng động: new
và delete
Cho đến nay, chúng ta chỉ thử tạo các đối tượng tự động, tức là các biến cục bộ bên trong OnStart
. Một đối tượng được khai báo trong bối cảnh toàn cục (bên ngoài OnStart
hoặc một hàm nào khác) cũng sẽ được tạo tự động (khi tập lệnh được tải) và bị xóa (khi tập lệnh được gỡ bỏ).
Ngoài hai chế độ này, chúng ta đã đề cập đến khả năng mô tả một trường của kiểu đối tượng (trong ví dụ của chúng ta, đây là cấu trúc Pair
được sử dụng cho trường coordinates
bên trong đối tượng Shape
). Tất cả các đối tượng như vậy cũng là tự động: chúng được trình biên dịch tạo ra cho chúng ta trong hàm tạo của một đối tượng "chủ" và bị xóa trong hàm hủy của nó.
Tuy nhiên, trong các chương trình, thường không thể chỉ sử dụng các đối tượng tự động. Trong trường hợp của một chương trình vẽ, chúng ta sẽ cần tạo các hình dạng theo yêu cầu của người dùng. Hơn nữa, các hình dạng sẽ cần được lưu trữ trong một mảng, và để làm điều này, các đối tượng tự động sẽ phải có một hàm tạo mặc định (điều này không đúng trong trường hợp của chúng ta).
Đối với những tình huống như vậy, MQL5 cung cấp cơ hội để tạo và xóa các đối tượng một cách động. Việc tạo được thực hiện với toán tử new
và việc xóa với toán tử delete
.
Toán tử new
Từ khóa new
được theo sau bởi tên của lớp cần thiết và, trong dấu ngoặc đơn, danh sách các đối số để gọi bất kỳ hàm tạo nào hiện có. Việc thực thi toán tử new
dẫn đến việc tạo một thể hiện của lớp.
Toán tử new
trả về một giá trị của kiểu đặc biệt — một con trỏ đến một đối tượng. Để mô tả một biến của kiểu này, thêm ký tự '*' sau tên lớp. Ví dụ:
Rectangle *pr = new Rectangle(100, 200, 50, 75, clrBlue);
Ở đây, biến pr
có kiểu là con trỏ đến một đối tượng của lớp Rectangle
. Con trỏ sẽ được thảo luận chi tiết hơn trong một phần riêng biệt.
Điều quan trọng cần lưu ý là việc khai báo một biến của kiểu con trỏ đối tượng không tự phân bổ bộ nhớ cho một đối tượng và không gọi hàm tạo của nó. Tất nhiên, một con trỏ chiếm không gian - 8 byte, nhưng thực tế, nó là một số nguyên không dấu ulong
, mà hệ thống diễn giải theo cách đặc biệt.
Bạn có thể làm việc với một con trỏ theo cách tương tự như với một đối tượng, tức là bạn có thể gọi các phương thức có sẵn thông qua toán tử giải tham chiếu và truy cập các trường.
Print(pr.toString());
Một biến con trỏ chưa được gán một mô tả đối tượng động (ví dụ, nếu toán tử new
không được gọi tại thời điểm khởi tạo một biến mới, mà được di chuyển đến một số dòng mã nguồn sau đó), chứa một con trỏ rỗng đặc biệt, được ký hiệu là NULL (để phân biệt với các số) nhưng thực tế bằng 0.
Toán tử delete
Các con trỏ nhận được qua new
nên được giải phóng vào cuối thuật toán bằng cách sử dụng toán tử delete
. Ví dụ:
delete pr;
Nếu điều này không được thực hiện, thể hiện được phân bổ bởi toán tử new
sẽ vẫn còn trong bộ nhớ. Nếu ngày càng nhiều đối tượng mới được tạo theo cách này, và sau đó không bị xóa khi không còn cần thiết, điều này sẽ dẫn đến việc tiêu tốn bộ nhớ không cần thiết. Các đối tượng động còn lại chưa được giải phóng gây ra các cảnh báo được in ra khi chương trình kết thúc. Ví dụ, nếu bạn không xóa con trỏ pr
, bạn sẽ nhận được điều gì đó như thế này trong log sau khi tập lệnh được gỡ bỏ:
1 undeleted object left
1 object of type Rectangle left
168 bytes of leaked memory
2
3
Thiết bị đầu cuối báo cáo có bao nhiêu đối tượng và thuộc lớp nào đã bị lập trình viên quên, cũng như chúng chiếm bao nhiêu bộ nhớ.
Khi toán tử delete
được gọi cho một con trỏ, con trỏ đó trở nên không hợp lệ vì đối tượng không còn tồn tại nữa. Một nỗ lực tiếp theo để truy cập các thuộc tính của nó gây ra lỗi thời gian chạy "Invalid pointer accessed":
Critical error while running script 'shapes (EURUSD,H1)'.
Invalid pointer access.
2
Chương trình MQL sau đó bị gián đoạn.
Tuy nhiên, điều này không có nghĩa là biến con trỏ đó không thể được sử dụng lại. Chỉ cần gán một con trỏ đến một thể hiện mới được tạo của đối tượng là đủ.
MQL5 có một hàm tích hợp cho phép kiểm tra tính hợp lệ của một con trỏ trong một biến — CheckPointer
:
ENUM_POINTER_TYPE CheckPointer(object *pointer);
Nó nhận một tham số là con trỏ đến một lớp kiểu và trả về một giá trị từ liệt kê ENUM_POINTER_TYPE:
- POINTER_INVALID — con trỏ không hợp lệ;
- POINTER_DYNAMIC — con trỏ hợp lệ đến một đối tượng động;
- POINTER_AUTOMATIC — con trỏ hợp lệ đến một đối tượng tự động.
Việc thực thi câu lệnh delete
chỉ có ý nghĩa đối với một con trỏ mà hàm trả về POINTER_DYNAMIC. Đối với một đối tượng tự động, nó sẽ không có hiệu lực (các đối tượng như vậy được xóa tự động khi quyền điều khiển trở về từ khối mã mà biến được định nghĩa).
Macro sau đơn giản hóa và đảm bảo việc dọn dẹp đúng cho một con trỏ:
#define FREE(P) if(CheckPointer(P) == POINTER_DYNAMIC) delete (P)
Sự cần thiết phải "dọn dẹp" một cách rõ ràng là cái giá không thể tránh khỏi phải trả cho sự linh hoạt mà các đối tượng động và con trỏ mang lại.