Sự cần thiết của việc khởi tạo không dùng constructor
(consteval.ca)Tôi không có constructor nhưng vẫn cần được khởi tạo
-
Mở đầu
- Khi mới học C++, người viết đã tìm hiểu về những trường hợp compiler cung cấp constructor mặc định.
- Từ đó nảy sinh băn khoăn về rủi ro đối tượng có thể không được khởi tạo trong một số tình huống nhất định.
-
Khởi tạo mặc định và khởi tạo giá trị
T t;thực hiện khởi tạo mặc định.- Nếu
Tlà kiểu lớp và có constructor mặc định thì nó sẽ được chạy. - Nếu
Tlà kiểu mảng thì từng phần tử sẽ được khởi tạo mặc định. - Nếu không thì sẽ không làm gì cả.
- Nếu
T t{};thực hiện khởi tạo giá trị.- Nếu
Tlà kiểu lớp thì sẽ khởi tạo mặc định nếu không có constructor mặc định hoặc có constructor mặc định do người dùng cung cấp hay đã bị xóa. - Nếu không thì trước tiên sẽ khởi tạo bằng 0 rồi mới khởi tạo mặc định.
- Nếu
Tlà kiểu mảng thì từng phần tử sẽ được khởi tạo giá trị. - Nếu không thì sẽ khởi tạo bằng 0.
- Nếu
-
Constructor mặc định
- Nếu không khai báo constructor mặc định thì compiler sẽ ngầm khai báo một constructor mặc định.
- Constructor mặc định được khai báo ngầm sẽ có phần thân rỗng và danh sách khởi tạo thành viên rỗng.
- Ví dụ:
struct T { int x; T() = default; }; T t{}; std::cout << t.x << std::endl; // kết quả in ra là 0
-
Constructor mặc định được định nghĩa ngầm
- Nếu constructor mặc định được khai báo ngầm hoặc được khai báo tường minh là mặc định, compiler sẽ cung cấp constructor mặc định được định nghĩa ngầm.
- Ví dụ:
struct T { T(); }; T::T() = default; T t{}; std::cout << t.x << std::endl; // kết quả in ra là giá trị rác
-
Các trường hợp không thể cung cấp constructor mặc định
Tcó thành viên tham chiếu không tĩnhTcó thành viên không tĩnh hoặc lớp cơ sở không trừu tượng không thể default construct hoặc không thể hủyTcó thành viên không tĩnhconstmà không có default member initializer
-
Khởi tạo đúng cách
T t{};thực hiện list-initialization.- List-initialization được chia thành direct-list-initialization và copy-list-initialization.
- Ví dụ:
struct S { int a; float b; char c; }; S s{3, 4.0f, 'S'}; // không gọi constructor
-
List-initialization và aggregate initialization
- Aggregate initialization là một dạng đặc biệt của list-initialization, trong đó từng phần tử của lớp hoặc mảng được copy-initialize từ từng phần tử tương ứng trong danh sách khởi tạo.
- Ví dụ:
struct A { const int x; }; A a{}; // a.x được khởi tạo thành 0
-
Khởi tạo bằng dấu ngoặc tròn
- Khởi tạo bằng dấu ngoặc tròn thực hiện direct non-list initialization.
- Ví dụ:
struct T { const int& r; }; T t(42); // t.r là tham chiếu trỏ tới 42
-
Tóm tắt
- Các quy tắc khởi tạo khá phức tạp, nhưng nếu tự viết constructor thì có thể tránh được phần lớn vấn đề.
- Thay vì phó mặc cho compiler, tốt hơn là nên tự viết constructor.
Ý kiến của GN⁺
- Bài viết giải thích khá rõ sự phức tạp của các quy tắc khởi tạo trong C++.
- Việc hiểu các quy tắc khởi tạo của C++ là rất quan trọng, vì chúng ảnh hưởng lớn đến độ ổn định và hiệu năng của mã.
- Tự viết constructor là cách tốt nhất để tránh các vấn đề khởi tạo.
- Một ngôn ngữ khác có tính năng tương tự là Rust, và Rust có các quy tắc khởi tạo rõ ràng hơn.
- Khi áp dụng công nghệ mới, điều quan trọng là phải hiểu rõ và sử dụng đúng những chi tiết như quy tắc khởi tạo.
1 bình luận
Ý kiến trên Hacker News
Kết quả khởi tạo của
tsẽ là 0tđược khởi tạo theo giá trị, và vìTkhông có constructor mặc định do người dùng định nghĩa, đối tượng sẽ được zero-initialize rồi mới gọi default constructorDefault constructor khởi tạo các thành viên theo kiểu default-initialize, khác với value-initialize
Có vẻ GCC cũng đồng ý với điều này
Tác giả đã bỏ sót việc thực ra đang value-initialize
xChi tiết của các quy tắc này rất phức tạp và đôi khi có phần phi lý
Nếu có cách biểu đạt tường minh cho default initialization thì sẽ là một cải tiến lớn
std::array<int, 100> = void;sẽ tốt hơnMối liên hệ giữa list initialization và aggregate initialization là khi list initialization được áp dụng cho aggregate thì aggregate initialization sẽ được thực hiện
Trường hợp chỉ có một phần tử hoạt động khác với trường hợp có từ hai phần tử trở lên
Có thể tự viết constructor của mình và khởi tạo tuple hoặc array chỉ với một phần tử được cung cấp
Khi danh sách khởi tạo của C++11 mới xuất hiện, đã phát hiện ra điều này và thấy nó thật điên rồ
Có nhắc đến "I Have No Mouth, and I Must Scream" (1967)
Sử dụng cú pháp
T::T() = default;Có thể sẽ kỳ vọng kết quả in ra là 0, nhưng thực tế lại là giá trị rác
Cho phép người dùng thư viện thay đổi hành vi của thư viện
Nếu muốn thêm độ phức tạp của C++, có thể tìm đọc C++ FQA
Chủ đề giao diện của blog lấy cảm hứng từ máy tính thời DEC nhưng vẫn gọn gàng và tối giản
Đọc nội dung này dễ thấy chóng mặt
Go và Rust không có constructor đặc biệt, nên nhiều thứ trở nên đơn giản hơn
Tò mò liệu có công cụ C++ nào cho thấy mọi hành vi ngầm định hay không
Bài viết cung cấp thông tin sai về lớp
Khẳng định rằng
T t;là "không làm gì cả" là saiT t;sẽ thất bạiPhần đầu blog có bảng điều khiển mặt trước của DEC