2011-07-25 47 views
36

Tại sao mọi người định nghĩa một hàm tạo bản sao riêng?Việc sử dụng hàm tạo bản sao riêng tư trong C++

Khi nào làm cho hàm tạo bản sao và toán tử gán có thiết kế tốt?

Nếu không có thành viên nào trong lớp là con trỏ hoặc xử lý đối tượng duy nhất (như tên tệp), thì các trường hợp khác ở đó là nơi mà hàm tạo bản sao riêng tư là một ý tưởng hay?

Câu hỏi tương tự áp dụng cho toán tử gán. Cho rằng đa số C++ xoay quanh việc sao chép các đối tượng và đi qua tham chiếu, có thiết kế tốt nào liên quan đến hàm tạo bản sao riêng không?

+0

Không có điều gì như một hàm tạo gán, bạn có ý là di chuyển hàm tạo không? – Praetorian

+0

đó là lỗi đánh máy ... sửa ngay bây giờ ... nhà điều hành chuyển nhượng –

+0

lý do chính tôi hỏi câu hỏi đó là tôi đã đọc sách C++ và tự mình học C++ ... và sách tôi đang đọc Lippman , Lajoie C++ Primer và Stroustrup và những người khác không cung cấp đủ các ví dụ thế giới thực, nơi cần có một cách tiếp cận như vậy. tất nhiên, không thể cung cấp danh sách đầy đủ ... nhưng một vài trường hợp cách tiếp cận như vậy sẽ hữu ích có thể được minh họa (ví dụ: ví dụ về xe hơi bằng @tc) và tôi đã tuyên bố rằng không có con trỏ hoặc liên kết với đối tượng duy nhất như một tập tin ....... –

Trả lời

27

Một số đối tượng đại diện cho các thực thể cụ thể không thể hoặc không nên sao chép. Ví dụ, bạn có thể ngăn chặn việc sao chép một đối tượng đại diện cho tệp nhật ký được ứng dụng sử dụng, tương ứng với kỳ vọng rằng một tệp nhật ký duy nhất sẽ được tất cả các phần của mã sử dụng. Việc sử dụng một đối tượng vô tình hoặc không được sao chép có thể dẫn đến nội dung không đúng thứ tự xuất hiện trong nhật ký, các bản ghi không chính xác của kích thước nhật ký hiện tại, nhiều lần thử (một số lỗi) để "cuộn" vào tên tệp nhật ký mới hoặc đổi tên tệp hiện có.

Sử dụng khác là thực thi sao chép thông qua chức năng ảo. Vì các hàm tạo không thể là virtual, một thực tế phổ biến là ngăn chặn truy cập trực tiếp vào trình tạo bản sao và cung cấp phương thức virtual Base* clone() trả về một bản sao của loại thời gian chạy thực tế mà con trỏ trỏ tới. Điều này ngăn chặn tình cờ cắt rằng Base b(derived) sẽ triển lãm.

Ví dụ khác: đối tượng con trỏ thông minh chết đơn giản chỉ xóa con trỏ được đưa ra trong hàm tạo: nếu nó không hỗ trợ tính tham chiếu hoặc một số cách xử lý nhiều chủ sở hữu khác và không muốn có rủi ro chuyển giao quyền sở hữu không mong muốn std::auto_ptr quyền sở hữu, sau đó chỉ đơn giản là ẩn các nhà xây dựng bản sao cho một con trỏ thông minh tuyệt vời đó là nhanh chóng và hiệu quả cho các trường hợp hạn chế, nơi nó có thể sử dụng. Một lỗi thời gian biên dịch về cố gắng sao chép nó có hiệu quả sẽ yêu cầu các lập trình viên "hey - nếu bạn thực sự muốn làm điều đó thay đổi tôi đến một con trỏ chia sẻ, nếu không trở lại!".

+0

khi khai báo bản sao c'tor là riêng tư, bạn vẫn cần tuân theo quy tắc của ba? có nghĩa là, bạn vẫn cần một toán tử gán và destructor? – ThunderWiring

+0

@ThunderWiring: trong ngắn hạn, loại bỏ các nhà điều hành chuyển nhượng thường là một ý tưởng tốt, nhưng destructor vẫn cần thiết bởi bất kỳ trường hợp được phép được tạo ra. Đối với phân công, có một số trường hợp không có vấn đề gì - như những singleton tự gán (là nhiệm vụ duy nhất có thể) là an toàn - nhưng điều đó hiếm khi hữu ích vì vậy có lẽ vẫn còn giá trị để cắt bỏ toán tử gán để tránh nhầm lẫn. –

0

Bạn có thể muốn triển khai một số phương thức của lớp bằng cách sử dụng một hàm tạo bản sao, nhưng không để lộ nó bên ngoài lớp. Vì vậy, sau đó bạn làm cho nó riêng tư. Giống như bất kỳ phương pháp nào khác.

+0

đó là câu hỏi của tôi ... chính xác tại sao? có thể có các lý do khác nhau ... sẽ rất hữu ích nếu bạn có thể cung cấp một số ví dụ ... –

33

Một trường hợp sử dụng là mẫu đơn trong đó chỉ có thể có chính xác một phiên bản của một lớp. Trong trường hợp này, bạn cần tạo các hàm tạo và toán tử gán = private để không có cách nào tạo nhiều hơn một đối tượng. Cách duy nhất để tạo một đối tượng là thông qua hàm GetInstance() của bạn như hình dưới đây.

// An example of singleton pattern 
class CMySingleton 
{ 
public: 
    static CMySingleton& GetInstance() 
    { 
    static CMySingleton singleton; 
    return singleton; 
    } 

// Other non-static member functions 
private: 
    CMySingleton() {}         // Private constructor 
    ~CMySingleton() {} 
    CMySingleton(const CMySingleton&);     // Prevent copy-construction 
    CMySingleton& operator=(const CMySingleton&);  // Prevent assignment 
}; 

int main(int argc, char* argv[]) 
{ 
    // create a single instance of the class 
    CMySingleton &object = CMySingleton::GetInstance(); 

    // compile fail due to private constructor 
    CMySingleton object1; 
    // compile fail due to private copy constructor 
    CMySingleton object2(object); 
    // compile fail due to private assignment operator 
    object1 = object; 

    // .. 
    return 0; 
} 
+2

ohk ... đó là một trường hợp .... ngay cả hàm khởi tạo sẽ là riêng tư trong trường hợp này .... –

1

Ngay cả khi nội dung của đối tượng không phải là con trỏ hoặc tham chiếu khác, ngăn người sao chép đối tượng vẫn có thể hữu ích. Có lẽ lớp học chứa rất nhiều dữ liệu và quá trình sao chép quá nặng.

+0

vâng tôi hiểu ... nhưng sau đó phải được để lại cho các lập trình viên phải không? thay vì làm cho nó bắt buộc không phải để sao chép các lập trình viên có thể truy cập các đối tượng bằng cách tham khảo .... –

+0

@Jayesh: Một API tốt là không thể (hoặc rất khó) để sử dụng sai. Bạn vẫn có thể truy cập đối tượng bằng cách tham chiếu, nhưng điều đó không có nghĩa là bạn nên được phép sao chép đối tượng đó. –

+0

tôi đoán bạn đúng ... một người bạn của tôi đã thuyết phục tôi về trường hợp cụ thể này ... –

4

Một ví dụ rất xấu:

class Vehicle : { int wheels; Vehicle(int w) : wheels(w) {} } 

class Car : public Vehicle { Engine * engine; public Car(Engine * e) : Vehicle(4), engine(e) } 

... 

Car c(new Engine()); 

Car c2(c); // Now both cars share the same engine! 

Vehicle v; 
v = c; // This doesn't even make any sense; all you have is a Vehicle with 4 wheels but no engine. 

có nghĩa là gì để "sao chép" một chiếc xe hơi? (Có phải ô tô là mô hình ô tô, hay một chiếc xe hơi? Sao chép nó có bảo quản việc đăng ký xe không?)

Điều gì có nghĩa là gán xe cho người khác?

Nếu các thao tác vô nghĩa (hoặc đơn giản là không thực hiện), điều chuẩn cần làm là làm cho hàm tạo bản sao và toán tử gán riêng, gây ra lỗi biên dịch nếu chúng được sử dụng thay vì hành vi lạ.

+0

Với vấn đề của xe ... Nếu chúng tôi cố gắng để lộ một chiếc xe như một chiếc xe, sau đó sẽ không có bất kỳ động cơ tương ứng với chiếc xe đó trong đối tượng xe đó .. và như vậy trong ý nghĩa ... bạn nên xây dựng một nhà xây dựng chuyên biệt cho chiếc xe mà sẽ xác định chỉ lấy bánh xe từ xe và xây dựng một đối tượng từ đó ... tôi sẽ nói, vấn đề là thiết kế lớp chứ không phải là điều xây dựng bản sao ... –

+2

@Jayesh Badwaik: Bạn đang yêu cầu các ví dụ thực tế. Thiết kế hơi xấu xí là khá phổ biến ở đó, giống như thực tế có xu hướng. Thậm chí không bắt đầu nhìn vào mã được lấy cảm hứng từ luật lệ cụ thể và hàng chục năm quan liêu. Thậm chí còn hữu ích hơn nữa để giữ sự xấu xí cục bộ. Cấm các bản sao có thể giúp ở đó: "lớp này hơi kỳ lạ, nhưng nó phù hợp với Phụ lục F11-M3" – MSalters

0

"virtual constructor idiom" là trường hợp quan trọng khi cần có trình tạo bản sao riêng tư hoặc được bảo vệ. Một vấn đề nảy sinh trong C++, nơi bạn được đưa con trỏ tới một lớp cơ sở, của một đối tượng thực sự được kế thừa từ lớp cơ sở này và bạn muốn tạo một bản sao của nó. Việc gọi hàm tạo bản sao sẽ không gọi hàm tạo bản sao của lớp kế thừa, nhưng thực sự gọi hàm tạo bản sao của lớp cơ sở.

Quan sát:

class Base { 

public: 
    Base(const Base & ref){ std::cout << "Base copy constructor" ; } 
}; 

class Derived : public Base { 

public: 
    Derived(const Derived & ref) : Base(ref) { std::cout << "Derived copy constructor"; } 
} 

Base * obj = new Derived; 
Base * obj2 = new Derived(*obj); 

Đoạn mã trên sẽ tạo ra kết quả:

"Base copy constructor" 

Đây rõ ràng không phải là hành vi lập trình viên muốn! Các lập trình viên đã cố gắng để sao chép một đối tượng thuộc loại "Có nguồn gốc" nhưng thay vào đó đã trở lại một đối tượng của loại "Base" !!

Sự cố được khắc phục bằng cách sử dụng thành ngữ đã nói ở trên. Quan sát các ví dụ viết ở trên, lại ghi vào sử dụng thành ngữ này:

class Base { 

public: 
    virtual Base * clone() const = 0; //this will need to be implemented by derived class 

protected: 
    Base(const Base & ref){ std::cout << "Base copy constructor" ; } 
}; 

class Derived : public Base { 

public: 
    virtual Base * clone() const { 

    //call private copy constructor of class "Derived" 
    return static_cast<Base *>(new Derived(*this)); 
    } 

//private copy constructor: 
private: 
    Derived(const Derived & ref) : Base(ref) { std::cout << "Derived copy constructor"; } 
} 

Base * obj = new Derived; 
Base * obj2 = obj->clone(); 

Đoạn mã trên sẽ tạo ra kết quả:

"Base copy constructor" 
"Derived copy constructor" 

Nói cách khác, các đối tượng đó được xây dựng vào loại mong muốn " Có nguồn gốc ", và không thuộc loại" Base "!

Như bạn có thể thấy, trong kiểu gốc, trình tạo bản sao được cố tình tạo riêng tư, vì nó sẽ là thiết kế API xấu để cho các lập trình viên khả năng vô tình cố gọi hàm sao chép theo cách thủ công, thay vì sử dụng giao diện thông minh được cung cấp bởi clone(). Nói một cách khác, một hàm tạo bản sao có thể gọi trực tiếp có thể khiến các lập trình viên mắc sai lầm được đề cập trong phần 1. Trong trường hợp này, thực hành tốt nhất sẽ có hàm khởi tạo sao chép được ẩn và chỉ truy cập gián tiếp bằng cách sử dụng phương thức "sao chép" () ".

+0

ví dụ đầu tiên của bạn không biên dịch vì không có downcasting ngầm trong c + + (Base * obj2 = new Derived (* obj) tạo ra một lỗi, * obj không thể được chuyển đổi hoàn toàn thành Derived &). Vì vậy, nó là không rõ ràng, một vấn đề bạn cố gắng để giải quyết với thành ngữ constructor ảo là gì – user396672

3

Lý do phổ biến để tạo bản sao chép và gán bản sao riêng tư là vô hiệu hoá việc thực hiện mặc định các hoạt động này. Tuy nhiên, trong C++ 0x có cú pháp đặc biệt = xóa cho mục đích đó. Vì vậy, trong C++ 0x làm bản sao ctor riêng dường như được sắp xếp lại cho các trường hợp rất kỳ lạ.

Sao chép ctors và bài tập là cú pháp khá đường; vì vậy một "đường tư nhân" như vậy có vẻ như là triệu chứng của tham lam :)

+0

:) .. cảm ơn cho các thông tin ..... tôi sẽ xem xét rằng ... cá nhân .. tôi cảm thấy rằng việc sử dụng của các nhà xây dựng bản sao nên được giữ ở mức tối thiểu và chắc chắn không được phổ biến cho toàn bộ thiết kế hệ thống (hiệu quả nên càng hạn chế càng tốt) .... càng lâu càng tốt ... ngay bây giờ tôi đang xử lý mức cao performace computational code ... vì vậy tôi đoán là không sao ... nhưng tôi vẫn cảm thấy nó sẽ tốt hơn nếu không có chúng ..... –

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