2014-12-01 34 views
5

Để hiểu được các ngữ nghĩa này chỉ được sử dụng với hàm tạo bản sao, di chuyển hàm khởi tạo, gán bản sao, gán chuyển động và hủy. Sử dụng = delete là để cấm sử dụng một trong các chức năng và = default được sử dụng nếu bạn muốn rõ ràng với trình biên dịch về nơi sử dụng các giá trị mặc định cho các chức năng này.Khi nào sử dụng = mặc định vs = xóa

Thực tiễn tốt nhất khi sử dụng các từ khóa này khi tạo lớp học là gì? Hay đúng hơn là làm cách nào để tôi chú ý đến những điều này khi phát triển một lớp học?

Ví dụ: nếu tôi không biết mình có sử dụng một trong các chức năng này hay không, tốt hơn là cấm nó với delete hoặc cho phép và sử dụng default?

+1

'= delete' có thể được sử dụng cho bất kỳ chức năng nào. – Jarod42

+2

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_. –

Trả lời

4

Tốt câu hỏi.

Cũng quan trọng: Nơi để sử dụng = default= 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à:

  1. destructor // this tells me the very most important things about this class.
  2. constructor mặc định
  3. copy constructor // I like to see my copy members together
  4. toán tử gán bản sao
  5. di chuyển constructor // I like to see my move members together
  6. 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_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.

+1

Tôi đã thử nghiệm với lời khuyên của bạn và tôi rất thích nó! Phần trên cùng với các thành viên dữ liệu cũng có vẻ là nơi tự nhiên để đặt các hàm riêng nhỏ để kiểm tra các điều kiện trước và sau và các bất biến, vì điều này cũng rất thông tin về loại trừu tượng mà lớp cung cấp. – TemplateRex

1

Bạn thường thấy = default khi bạn đang cố gắng duy trì rule of 5 để đảm bảo các chức năng thành viên đặc biệt hoạt động như bạn định, và để người đọc của lớp có thể thấy rằng bạn đã xem xét cách bạn muốn hàm đó hoạt động .

Bạn có thể sử dụng = delete nếu bạn định tạo một thứ gì đó không thể sao chép hoặc không thể di chuyển, ví dụ. Mặc dù tôi cũng đã thấy mọi người delete một hàm được thừa hưởng nếu họ không muốn lớp dẫn xuất cụ thể đó có hàm đó, mặc dù tôi không phải là một fan lớn vì nó có xu hướng hướng tới kiến ​​trúc/thiết kế kém.

+0

Mặc định có ý nghĩa đối với tôi về nơi sử dụng nó đúng cách, nhưng bạn có nghĩ rằng việc xem một thứ gì đó không thể sao chép được nhưng có thể di chuyển được không? hoặc ngược lại? Tôi muốn được nhìn thấy một số ví dụ nếu bạn biết chính xác nơi để chỉ cho tôi! :) – flakes

+2

@Calpratt: std :: unique_ptr có thể di chuyển nhưng không thể sao chép được. – erenon

+0

@Calpratt Và hầu hết các lớp giá trị đều có thể đồng bộ hóa, nhưng không thể di chuyển được. Bạn thường chỉ bận tâm để làm cho một cái gì đó di chuyển 1) nếu bạn cần các ngữ nghĩa chuyển giao đặc biệt (trong trường hợp đó, nó là _not_ copiable), hoặc 2) profiler cho thấy rằng sao chép là một nút cổ chai trong ứng dụng của bạn. –

4

Ngoài ra, sử dụng =default thay vì một cuộn bằng tay giữ bản chất POD của lớp, vì nó được mô tả ở đây một cách chi tiết: Default constructors and POD

+1

Đây thực sự là câu trả lời chính xác liên quan đến '= mặc định'. Nó gần như luôn luôn được sử dụng bởi vì bạn muốn có hành vi mặc định (bao gồm, có thể, POD-ness), nhưng không muốn hàm đó là 'public'. –

Các vấn đề liên quan