21. Tránh việc khởi tạo phụ thuộc qua lại giữa những đơn vị trình biên dịch

Estimated reading: 5 minutes 220 views

Tóm tắt

Giữ thứ tự khởi tạo: Các đối tượng cấp độ namespace (Namespace-level objects) trong các đơn vị biên dịch (compilation units) khác nhau không bao giờ được phép phụ thuộc lẫn nhau để khởi tạo, vì thứ tự khởi tạo của chúng không được xác định. Vi phạm nguyên tắc này có thể gây ra nhiều rắc rối, từ các lỗi crash khó hiểu khi bạn thực hiện những thay đổi nhỏ trong dự án, cho đến mất khả năng tương thích nghiêm trọng với các phiên bản mới của cùng một trình biên dịch.

Thảo luận

Khi bạn định nghĩa hai đối tượng cấp độ namespace trong các đơn vị biên dịch khác nhau, thứ tự gọi hàm tạo (constructor) của chúng không được xác định. Thông thường (nhưng không phải mọi lúc), trình biên dịch có thể khởi tạo chúng theo thứ tự liên kết của các file đối tượng trong quá trình biên dịch, nhưng giả định này thường không đáng tin cậy. Ngay cả khi nó đúng trong một số trường hợp, bạn cũng không muốn sự đúng đắn của mã nguồn phụ thuộc một cách tinh vi vào tệp makefile hoặc tệp project của bạn (Để hiểu rõ hơn về tác hại của sự phụ thuộc vào thứ tự khởi tạo, hãy xem Mục 59).

Do đó, bên trong mã khởi tạo của bất kỳ đối tượng cấp độ namespace nào, bạn không thể giả định và “tự sướng” rằng toàn bộ các đối tượng khác được định nghĩa trong một đơn vị biên dịch khác đã được khởi tạo trước đó. Những cân nhắc này cũng áp dụng cho các biến kiểu nguyên thủy được khởi tạo động, chẳng hạn như:

				
					namespace {  
    bool reg_success = LibRegister("mylib");  
}				
			

Lưu ý rằng, ngay cả trước khi được khởi tạo bằng hàm tạo constructor, các đối tượng cấp  độ namespace sẽ được khởi tạo tĩnh với giá trị về 0 (zero-initialization); trái ngược với các đối tượng tự động, ban đầu có thể chứa dữ liệu rác. Trớ trêu thay, việc khởi tạo về 0 này có thể khiến lỗi trở nên khó phát hiện. Thay vì làm chương trình của bạn sập ngay lập tức, nó lại tạo ra ảo giác rằng đối tượng chưa được khởi tạo thực ra vẫn hợp lệ. Bạn có thể nghĩ rằng chuỗi (string) đang rỗng, con trỏ (point) đang là null, hoặc số nguyên (integer) đang là 0, trong khi thực tế là không có đoạn mã nào của bạn thực sự khởi tạo chúng cả.

Để tránh vấn đề này, hãy hạn chế sử dụng các biến cấp namespace càng nhiều càng tốt, vì chúng tiềm ẩn nhiều rủi ro (Mục 10). Nếu bắt buộc phải dùng một biến có thể phụ thuộc vào biến khác, hãy cân nhắc sử dụng mẫu thiết kế Singleton (Singleton design pattern). Sử dụng cẩn thận, có thể giúp tránh các phụ thuộc ngầm bằng cách đảm bảo đối tượng chỉ được khởi tạo khi lần đầu tiên được truy cập. Tuy nhiên, Singleton thực chất vẫn là một biến toàn cục được “ngụy trang” (Mục 10) và có thể bị phá vỡ khi gặp các phụ thuộc lẫn nhau hoặc vòng lặp phụ thuộc (một lần nữa, việc khởi tạo về 0 chỉ khiến mọi thứ thêm phức tạp).

Tham khảo

[DewhurstO3] §55 • [Gamma95] • [McConnell93] §5.1-4 • [StronstrupOO] §9.4.1, §10.4.9

Link

Leave a Comment

Chia sẻ:

21. Tránh việc khởi tạo phụ thuộc qua lại giữa những đơn vị trình biên dịch

Or copy link

CONTENTS