2013-04-30 29 views
7

Hãy xem xét các đối tượng:Const đúng đắn với các đối tượng có chứa shared_ptr

class Obj 
{ 
    public: 
     Obj() : val(new int(1)) {} 
     int& get() {return *val;} 
     const int& get() const {return *val;} 

    private: 
     std::shared_ptr<int> val; 
}; 

Đúng như dự đoán, khi đối tượng được xây dựng và bản sao được thực hiện tất cả họ đều có thể thay đổi giá trị tương tự thông qua việc tiếp xúc với shared_ptr bởi obj.

Obj nonconst1; 
    Obj nonconst2(nonconst1); 
    nonconst2.get() = 2; 
    cout << nonconst1.get() << ", " << nonconst2.get() << endl; 

Nó cũng có thể sao chép-xây dựng một đối tượng const Obj từ một trong những phi const, mà dường như để làm điều đúng ở chỗ nó cho phép đọc nhưng không phải bằng văn bản cho giá trị - như mong đợi kết quả mã sau vào lỗi biên dịch:

const Obj const1(nonconst1); 
    const1.get() = 3; 

Tuy nhiên, có thể sao chép không tạo thành Obj từ const, sau đó cho phép sửa đổi giá trị.

Obj nonconst3(const1); 
    nonconst3.get() = 3; 

Đối với tôi điều này không cảm thấy chính xác.

Có cách nào để ngăn chặn hành vi này, trong khi vẫn cho phép trình tạo bản sao hoạt động không? Trong trường hợp sử dụng thực sự của tôi, tôi vẫn muốn container std của Obj là có thể.

+0

Có thể, bạn nên xác định trình tạo bản sao tham chiếu không tham chiếu để tránh sao chép các đối tượng 'const'? – Nawaz

+0

@Nawaz, hoạt động (tôi đã không nhận ra rằng nó có thể sử dụng một ref không-const trong một hàm tạo bản sao). Thật không may tôi cũng muốn const Obj để có thể sao chép xây dựng khác const obj nhưng cố gắng 'Obj (const Obj & o) const' không biên dịch – tangobravo

Trả lời

1

Không, không có, trừ khi bạn muốn lưu trữ shared_ptr<const int>, trong trường hợp này không ai có thể truy cập nó dưới dạng không const.

0

Cài đặt thủ công một hàm tạo bản sao của Obj mà sau đó sẽ sao chép nội dung của con trỏ được chia sẻ. Điều này tránh sửa đổi nội dung của const1 qua nonconst3, vì chúng trỏ đến các phiên bản int khác nhau.

Tuy nhiên, bạn muốn tránh các bản sao sâu của không phải là trường hợp của Obj (nơi không có vấn đề và dự định sử dụng lại con trỏ được chia sẻ cũ). Đối với điều này, bạn phải cung cấp cả const và không const nhà xây dựng bản sao và chỉ sao chép trong const một:

class Obj 
{ 
    public: 
    //... 
    Obj(Obj &o) : val(o.val) {}       // not a deep copy 
    Obj(const Obj &o) : val(std::make_shared(o.get())) {} // deep copy 
    //... 
} 
2

"Đối với tôi điều này không cảm thấy const-đúng" nhưng nó là: bạn chỉ đơn giản gọi phương thức không const get trên một non const Obj. Không có gì sai với điều đó.

Nếu bạn thực sự cần hành vi mà bạn đang sau, bạn có thể sử dụng một cái gì đó giống như một proxy const để Obj nhưng sau đó khách hàng của bạn phải có khả năng xử lý nó tất nhiên:

class Obj 
{ 
    //... 
    //original class definition goes here 
    //... 
    friend class ConstObj; 
}; 

class ConstObj 
{ 
    public: 
    ConstObj(const Obj& v) : val(v.val) {} 
    const int& get() const { return *val; } 

    private: 
    std::shared_ptr<int> val; 
}; 

//usage: 
class WorkingWithObj 
{ 
public: 
    WorkingWithObj(); 
    Obj DoSomethingYieldingNonConstObj(); 
    ConstObj DoSomethingYieldingConstObj(); 
}; 

WorkingWithObj w; 
Obj nonconst(w.DoSomethingYieldingNonConstObj()); 
nonconst.get() = 3; 

ConstObj veryconst(nonconst); 
veryconst.get() = 3; //compiler error 

ConstObj alsoconst(w.DoSomethingYieldingConstObj()); 
alsoconst.get() = 3; //compiler error 
+1

Bạn có thể giải thích câu trả lời của bạn nhiều hơn một chút? Tôi không thấy gì trong 'Obj' và mẫu sử dụng? – Nawaz

+0

@Nawaz đã thêm một số nhận xét/ví dụ sử dụng. Mặc dù tôi phải nói rằng tôi có thể không bao giờ sử dụng các cấu trúc như thế này trong mã thực .. – stijn

0

Không, có isn 't ... Nhưng bạn có thể sử dụng COW, deep-copy con trỏ, khi bạn có thể viết thư cho value (trong bộ thu thập không const).

Hoặc, bạn có thể viết hai copy-ctors (cho ref làm bản sao nông, để cref làm bản sao sâu).

A(A& obj) : pointer(obj.pointer) {} 
    A(const A& obj) : pointer(new int(*obj.pointer)) {} 
1

Điều đó không phá vỡ tính chính xác của const. Đối tượng số nguyên được trỏ đến bởi val là một đối tượng riêng biệt, không thuộc sở hữu độc quyền của đối tượng gốc. Sửa đổi giá trị của nó không ảnh hưởng đến trạng thái của các đối tượng Obj.

1

Is there a way to prevent this behaviour, whilst still allowing the copy constructor to work? In my real use case, I still want std containers of Obj to be possible.

Bạn có thể chỉ định một constructor sao chép khác nhau để sao chép từ một đối tượng const - có nghĩa là bạn có thể ví dụ tránh sao chép các con trỏ được chia sẻ và thay vào đó tạo ra các đối tượng không const với một con trỏ NULL, hoặc bạn có thể làm một bản sao sâu của số chỉ đến. Tôi rất cảnh giác khi làm những việc như thế này - thật kỳ lạ khi có những hành vi khác nhau tùy thuộc vào độ chói của biến được sao chép - tôi sợ nó sẽ làm cho nó rất khó để giải thích về hành vi của chương trình của bạn. Tuy nhiên, bạn phải chọn một số hành vi hoặc chấp nhận hành vi hiện tại vì std::vector<> sẽ tạo bản sao đôi khi - bạn không thể đơn giản để nó không được xác định.