Tốt câu hỏi.
Cũng quan trọng: Nơi để sử dụng = default
và = delete
.
Tôi có một số lời khuyên gây tranh cãi về vấn đề này. Nó mâu thuẫn với những gì chúng tôi tất cả đã học (bao gồm cả bản thân mình) cho C++ 98/03.
Bắt đầu khai của bạn lớp với các thành viên dữ liệu của bạn:
class MyClass
{
std::unique_ptr<OtherClass> ptr_;
std::string name_;
std::vector<double> data_;
// ...
};
Sau đó, càng gần càng được thực tế, liệt kê tất cả trong số sáu thành viên đặc biệt mà bạn muốn tuyên bố một cách rõ ràng, và theo một thứ tự dự đoán được (và không liệt kê những cái bạn muốn trình biên dịch xử lý). Trình tự tôi thích là:
- destructor
// this tells me the very most important things about this class.
- constructor mặc định
- copy constructor
// I like to see my copy members together
- toán tử gán bản sao
- di chuyển constructor
// I like to see my move members together
- toán tử gán di chuyển
Lý do cho đơn đặt hàng này là:
- Bất kỳ thành viên đặc biệt nào bạn mặc định, người đọc có nhiều khả năng hiểu những gì mặc định làm nếu họ biết thành viên dữ liệu là gì.
- Bằng cách liệt kê các thành viên đặc biệt ở một nơi phù hợp gần đầu, và theo một thứ tự nhất quán, người đọc có nhiều khả năng để nhanh chóng nhận ra đó các thành viên đặc biệt là không khai báo rõ ràng ‐ và do đó một trong hai ngầm tuyên bố, hoặc không không tồn tại chút nào.
- Thông thường cả hai thành viên sao chép (hàm tạo và phép gán) đều giống nhau. Cả hai sẽ được mặc định hoặc xóa hoàn toàn, được mặc định rõ ràng hoặc bị xóa hoặc được cung cấp rõ ràng. Nó là tốt đẹp để xác nhận điều này trong hai dòng mã ngay bên cạnh nhau.
- Thông thường cả các thành viên di chuyển (constructor và phân công) cũng tương tự như ...
Ví dụ:
class MyClass
{
std::unique_ptr<OtherClass> ptr_;
std::string name_;
std::vector<double> data_;
public:
MyClass() = default;
MyClass(const MyClass& other);
MyClass& operator=(const MyClass& other);
MyClass(MyClass&&) = default;
MyClass& operator=(MyClass&&) = default;
// Other constructors...
// Other public member functions
// friend functions
// friend types
// private member functions
// ...
};
Biết được quy ước, người ta có thể nhanh chóng xem, mà không cần phải kiểm tra toàn bộ khai báo lớp rằng ~MyClass()
được ngầm định mặc định, và với các thành viên dữ liệu gần đó, nó rất dễ dàng để xem những gì mà trình biên dịch khai báo và cung cấp destructor nào. Tiếp theo chúng ta có thể thấy rằng MyClass
có một constructor mặc định được mặc định rõ ràng, và với các thành viên dữ liệu được khai báo gần đó, nó rất dễ dàng để xem những gì mà trình biên dịch cung cấp mặc định constructor nào. Cũng dễ thấy lý do tại sao hàm tạo mặc định đã được khai báo rõ ràng: Vì chúng ta cần một hàm tạo bản sao do người dùng xác định và điều đó sẽ ngăn cản một hàm tạo mặc định do trình biên dịch cung cấp nếu không được mặc định rõ ràng.
Tiếp theo chúng ta thấy rằng có một nhà xây dựng bản sao do người dùng cung cấp và nhà điều hành gán bản sao. Tại sao? Vâng, với các thành viên dữ liệu gần đó, thật dễ dàng để suy đoán rằng có lẽ một bản sao sâu của unique_ptr ptr_
là cần thiết. Chúng tôi không thể biết rằng chắc chắn tất nhiên mà không kiểm tra định nghĩa của các thành viên sao chép. Nhưng ngay cả khi không có những định nghĩa này, chúng tôi đã được thông báo khá tốt.
Với các thành viên sao chép do người dùng khai báo, các thành viên di chuyển sẽ hoàn toàn không được khai báo nếu chúng tôi không làm gì cả. Nhưng ở đây chúng ta dễ dàng nhìn thấy (bởi vì mọi thứ được dự đoán theo nhóm và được sắp xếp ở đầu tờ khai MyClass
) mà chúng ta đã mặc định di chuyển các thành viên. Và một lần nữa, vì các thành viên dữ liệu ở gần đó, chúng tôi có thể ngay lập tức xem những thành viên di chuyển do trình biên dịch cung cấp sẽ làm gì.
Tóm lại, chúng tôi chưa có đầu mối chính xác những gì MyClass
thực hiện và vai trò nào sẽ phát trong chương trình này. Tuy nhiên, ngay cả thiếu kiến thức đó, chúng tôi đã biết rất nhiều về MyClass
.
Chúng ta biết MyClass
:
- Giữ một con trỏ sở hữu duy nhất đối với một số (có thể là đa hình)
OtherClass
.
- Giữ một chuỗi phân phát dưới dạng tên.
- Giữ một loạt các đôi bị cắt xén như một số loại dữ liệu.
- Tự hủy chính xác mà không bị rò rỉ.
- Sẽ mặc định xây dựng chính nó bằng một số trống
ptr_
, trống name_
và data_
.
- Sẽ tự sao chép chính xác, không tích cực chính xác như thế nào, nhưng có một thuật toán có khả năng mà chúng tôi có thể dễ dàng kiểm tra ở nơi khác.
- Sẽ hiệu quả (và chính xác) di chuyển chính nó bằng cách di chuyển từng thành viên trong ba thành viên dữ liệu.
Rất nhiều điều cần biết trong vòng 10 dòng mã. Và chúng tôi không phải đi săn qua hàng trăm dòng mã mà tôi chắc chắn là cần thiết để thực hiện đúng cách MyClass
để tìm hiểu tất cả điều này: bởi vì tất cả đều ở trên cùng và theo thứ tự có thể dự đoán được.
Có thể bạn muốn tinh chỉnh công thức này để đặt các loại lồng nhau trước các thành viên dữ liệu để các thành viên dữ liệu có thể được khai báo theo loại lồng nhau. Tuy nhiên, tinh thần của đề xuất này là để khai báo các thành viên dữ liệu cá nhân, và các thành viên đặc biệt, cả hai đều gần với đầu như thực tế, và gần gũi với nhau như thực tế. Điều này chạy ngược lại với những lời khuyên được đưa ra trong quá khứ (có lẽ ngay cả bản thân tôi), rằng các thành viên dữ liệu riêng tư là một chi tiết thực hiện, không quan trọng, đủ để ở trên cùng của khai báo lớp.
Nhưng ở chế độ hindsight (hindsight luôn là 20/20), thành viên dữ liệu riêng, mặc dù không thể truy cập được mã xa (đó là điều tốt) làm đọc và mô tả hành vi cơ bản của một loại các thành viên đặc biệt được cung cấp bởi trình biên dịch. Và biết những thành viên đặc biệt của một lớp học là gì, là một trong những khía cạnh quan trọng nhất trong việc hiểu mọi loại.
- Có thể phá hủy không?
- Thiết bị có thể định cấu hình mặc định không?
- Có thể sao chép không?
- Dịch vụ có thể di chuyển không?
- Nó có ngữ nghĩa giá trị hoặc ngữ nghĩa tham chiếu?
Mọi loại đều có câu trả lời cho những câu hỏi này và cách tốt nhất là nhận các câu hỏi này & câu trả lời ngoài cách ASAP. Sau đó, bạn có thể dễ dàng tập trung vào những gì làm cho loại này khác với mọi loại khác.
'= delete' có thể được sử dụng cho bất kỳ chức năng nào. – Jarod42
Nếu bạn không biết mình có sử dụng một trong các hàm hay không, hãy _find out_. –