2012-06-26 20 views
6

Gần đây tôi đã xem xét lại các nhà xây dựng bản sao, toán tử gán, sao chép swap IDOM nhìn thấy ở đây: What is the copy-and-swap idiom? và nhiều nơi khác -Sao chép constructor và thực hiện toán tử gán sự lựa chọn -

Liên kết Trên đây là một bài tuyệt vời - nhưng tôi vẫn đã có một vài câu hỏi nữa - những câu hỏi này được trả lời trong một loạt các địa điểm, trên stackoverflow và nhiều trang web khác, nhưng tôi đã không nhìn thấy rất nhiều quán -

1 - Nếu bạn có try-catch xung quanh khu vực nơi chúng tôi cấp phát bộ nhớ mới cho bản sao sâu trong bản sao lưu tructor? (Ive thấy cả hai cách)

2 - Liên quan đến kế thừa cho cả hàm tạo bản sao và toán tử gán, khi nào các hàm lớp cơ sở được gọi và khi nào các hàm này sẽ là ảo?

3 - Có phải std::copy cách tốt nhất để nhân bản bộ nhớ trong trình tạo bản sao? Tôi đã thấy nó với memcpy, và thấy những người khác nói rằng memcpy điều tồi tệ nhất trên trái đất.


Hãy xem xét ví dụ dưới đây (Cảm ơn tất cả những phản hồi), nó nhắc nhở một số câu hỏi thêm:

4 - nên chúng tôi được kiểm tra để tự chuyển nhượng? Nếu vậy nơi

5 - Tắt câu hỏi chủ đề, nhưng tôi đã thấy hoán đổi sử dụng như: std::copy(Other.Data,Other.Data + size,Data); nó nên là: std::copy(Other.Data,Other.Data + (size-1),Data); nếu swap đi từ 'đầu đến cuối và các yếu tố 0 là Other.Data?

6 - Tại sao công cụ xây dựng không nhận xét (tôi phải thay đổi kích thước thành mysize) - giả sử điều này có nghĩa là bất kể thứ tự tôi viết, hàm khởi tạo sẽ luôn gọi phần tử phân bổ trước?

7 - Bất kỳ nhận xét nào khác về việc triển khai của tôi? Tôi biết mã là vô ích nhưng tôi chỉ cố gắng minh họa một điểm.

class TBar 
{ 

    public: 

    //Swap Function   
    void swap(TBar &One, TBar &Two) 
    { 
      std::swap(One.b,Two.b); 
      std::swap(One.a,Two.a); 
    } 

    int a; 
    int *b; 


    TBar& operator=(TBar Other) 
    { 
      swap(Other,*this); 
      return (*this); 
    } 

    TBar() : a(0), b(new int) {}    //We Always Allocate the int 

    TBar(TBar const &Other) : a(Other.a), b(new int) 
    { 
      std::copy(Other.b,Other.b,b); 
      *b = 22;            //Just to have something 
    } 

    virtual ~TBar() { delete b;} 
}; 

class TSuperFoo : public TBar 
{ 
    public: 

    int* Data; 
    int size; 

    //Swap Function for copy swap 
    void swap (TSuperFoo &One, TSuperFoo &Two) 
    { 
      std::swap(static_cast<TBar&>(One),static_cast<TBar&>(Two)); 
      std::swap(One.Data,Two.Data); 
      std::swap(One.size,Two.size); 
    } 

    //Default Constructor 
    TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[mysize]) {} 
    //TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[size]) {}    *1 

    //Copy Constructor 
    TSuperFoo(TSuperFoo const &Other) : TBar(Other), size(Other.size), Data(new int[Other.size])  // I need [Other.size]! not sizw 
    { 
      std::copy(Other.Data,Other.Data + size,Data);  // Should this be (size-1) if std::copy is First -> Last? *2 
    } 

    //Assignment Operator 
    TSuperFoo& operator=(TSuperFoo Other) 
    { 
      swap(Other,(*this)); 
      return (*this); 
    } 

    ~TSuperFoo() { delete[] Data;} 

}; 

Trả lời

4
  1. Nếu bạn phân bổ bộ nhớ thì bạn cần phải đảm bảo rằng nó được giải phóng trong trường hợp ngoại lệ được ném. Bạn có thể làm điều này với một số try/catch rõ ràng hoặc bạn có thể sử dụng con trỏ thông minh như std::unique_ptr để giữ bộ nhớ, sau đó sẽ tự động bị xóa khi con trỏ thông minh bị phá hủy bởi ngăn xếp thư giãn.

  2. Bạn rất hiếm khi cần một nhà điều hành chuyển nhượng virtual. Gọi hàm tạo bản sao của lớp cơ sở trong danh sách khởi tạo thành viên và toán tử gán lớp cơ sở đầu tiên trong toán tử gán có nguồn gốc nếu bạn đang thực hiện chuyển nhượng thành viên --- nếu bạn đang sao chép/hoán đổi thì bạn không cần gọi phân bổ lớp cơ sở trong toán tử gán có nguồn gốc của bạn, miễn là bản sao và trao đổi được triển khai chính xác.

  3. std::copy hoạt động với các đối tượng và sẽ gọi chính xác các nhà xây dựng sao chép. Nếu bạn có các đối tượng POD đơn giản thì memcpy cũng sẽ hoạt động. Tôi muốn đi cho std::copy trong hầu hết các trường hợp mặc dù --- nó nên được tối ưu hóa để memcpy dưới mui xe anyway cho PODs, và nó tránh được tiềm năng cho các lỗi bạn nên thêm một bản sao constructor sau này.

[cập nhật cho câu hỏi được cập nhật]

  1. Với copy/hoán đổi như bằng văn bản không có cần phải kiểm tra để tự chuyển nhượng, và thực sự không có cách nào làm như vậy --- do thời gian bạn nhập toán tử gán othersao chép và bạn không có cách nào biết được đối tượng nguồn là gì. Điều này chỉ có nghĩa là tự gán sẽ vẫn làm một bản sao/trao đổi.

  2. std::copy có một cặp vòng lặp (đầu tiên, đầu tiên + kích thước) làm đầu vào. Điều này cho phép các khoảng trống và giống như mọi thuật toán dựa trên dải ô trong thư viện chuẩn.

  3. Trình tạo ra nhận xét không hoạt động vì các thành viên được khởi tạo theo thứ tự được khai báo, bất kể thứ tự trong danh sách bộ khởi tạo thành viên. Do đó, Data luôn được khởi tạo trước tiên. Nếu khởi tạo phụ thuộc vào size thì nó sẽ nhận được một giá trị duff kể từ size chưa được khởi tạo. Nếu bạn hoán đổi các khai báo của sizedata thì hàm tạo này sẽ hoạt động tốt. Trình biên dịch tốt sẽ cảnh báo về thứ tự khởi tạo thành viên không khớp với thứ tự khai báo.

+0

Cảm ơn - Đã cập nhật với ví dụ – MikeyG

1
  1. Nếu các nhà xây dựng cho những gì bạn đang sao chép sâu có thể ném một cái gì đó bạn có thể xử lý, đi trước và nắm bắt nó. Tuy nhiên, tôi chỉ để cho bộ nhớ ngoại lệ phân bổ truyền bá.
  2. Sao chép các nhà xây dựng (hoặc bất kỳ nhà thầu) nào không được ảo. Bao gồm một bộ khởi tạo lớp cơ sở cho các loại này. Sao chép toán tử gán phải ủy quyền cho lớp cơ sở ngay cả khi chúng là ảo.
  3. memcpy() là quá thấp để sao chép các loại lớp trong C++ và có thể dẫn đến hành vi không xác định. Tôi nghĩ rằng std::copy thường là lựa chọn tốt hơn.
1
  1. try-catch có thể được sử dụng khi bạn phải lùi lại một cái gì đó. Nếu không, chỉ cần để tuyên bố bad_alloc cho người gọi.

  2. Gọi trình tạo bản sao của lớp cơ sở hoặc toán tử gán là cách tiêu chuẩn để cho phép xử lý việc sao chép của nó. Tôi chưa bao giờ nhìn thấy một trường hợp sử dụng cho một nhà điều hành gán ảo, vì vậy tôi đoán chúng là hiếm.

  3. std::copy có lợi thế là nó sao chép chính xác đối tượng lớp. memcpy là khá hạn chế về những loại nó có thể xử lý.

3

1 - Nếu bạn có try-catch xung quanh những khu vực mà chúng tôi phân bổ bộ nhớ mới cho một bản sao sâu trong constructor sao chép?

Nói chung, bạn chỉ nên bắt ngoại lệ nếu bạn có thể xử lý. Nếu bạn có cách xử lý tình trạng thiếu bộ nhớ cục bộ, hãy bắt nó; nếu không, hãy để nó đi.Bạn chắc chắn sẽ không trở lại bình thường từ một nhà xây dựng nếu xây dựng đã thất bại - điều đó sẽ để người gọi với một đối tượng không hợp lệ, và không có cách nào để biết rằng nó không hợp lệ.

2 - Liên quan đến kế thừa cho cả hàm tạo bản sao và toán tử gán, khi nào các hàm lớp cơ sở được gọi và khi nào các hàm này sẽ là ảo?

Một hàm tạo không thể ảo vì các hàm ảo chỉ có thể được gửi đi bởi một đối tượng và không có đối tượng nào trước khi bạn tạo đối tượng đó. Thông thường, bạn sẽ không tạo các toán tử gán ảo; các lớp có thể sao chép và gán được thường được coi là loại "giá trị" không đa hình.

Thông thường, bạn muốn gọi constructor lớp cơ sở sao chép từ danh sách initialiser:

Derived(Derived const & other) : Base(other), <derived members> {} 

và nếu bạn đang sử dụng thành ngữ sao chép và trao đổi, sau đó toán tử gán của bạn sẽ không cần phải lo lắng về lớp cơ sở; mà có thể được xử lý bởi các swap:

void swap(Derived & a, Derived & b) { 
    using namespace std; 
    swap(static_cast<Base&>(a), static_cast<Base&>(b)); 
    // and swap the derived class members too 
} 
Derived & Derived::operator=(Derived other) { 
    swap(*this, other); 
    return *this; 
} 

3 - Là std::copy cách tốt nhất để sao chép bộ nhớ trong constructor sao chép? Tôi đã thấy nó với memcopy, và thấy những người khác nói rằng memcopy điều tồi tệ nhất trên trái đất.

Khá khác thường khi xử lý bộ nhớ nguyên; thường thì lớp của bạn chứa các đối tượng và thường các đối tượng không thể được sao chép chính xác bằng cách đơn giản là sao chép bộ nhớ của chúng. Bạn sao chép các đối tượng bằng cách sử dụng các hàm tạo bản sao hoặc các toán tử gán, và std::copy sẽ sử dụng toán tử gán để sao chép một mảng các đối tượng (hoặc, thường là một chuỗi các đối tượng).

Nếu bạn thực sự muốn, bạn có thể sử dụng memcpy để sao chép POD (đối tượng dữ liệu cũ) và mảng; nhưng std::copy ít bị lỗi hơn (vì bạn không cần phải cung cấp kích thước đối tượng), ít mong manh hơn (vì nó sẽ không bị hỏng nếu bạn thay đổi các đối tượng thành không phải POD) và có khả năng nhanh hơn (vì kích thước đối tượng và liên kết được biết tại thời gian biên dịch).

+0

Cảm ơn - Tôi đã cập nhật câu hỏi bằng ví dụ và một số thảo luận thêm, Vui lòng xem – MikeyG

+1

@MikeyG: Đó là khá nhiều câu hỏi; bạn nên yêu cầu họ một cách riêng biệt hơn. Tóm lại: (4) bạn không cần phải xử lý tự gán nếu bạn đang sử dụng sao chép và trao đổi; (5) 'std :: copy' (và trong các hàm chung dùng các dãy vòng lặp) mong đợi trình lặp" kết thúc "được * quá khứ * kết thúc chuỗi; (6) các thành viên luôn được khởi tạo theo thứ tự chúng được khai báo trong định nghĩa lớp, vì vậy 'Dữ liệu' được khởi tạo trước' kích thước'. –

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