Correctness of constants with objects containing shared_ptr

Consider the object:

class Obj { public: Obj() : val(new int(1)) {} int& get() {return *val;} const int& get() const {return *val;} private: std::shared_ptr<int> val; }; 

As expected, when the object is designed and made copies, all of them can change the same value through shared_ptr, opened by Obj.

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

It is also possible to copy-build the const Obj object from one of the non const, which seems to do the right thing, since it allows you to read, but not write to a value - as expected, the following code leads to a compilation error:

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

However, you can copy-build a non-const Obj from const const, which then allows you to change the value.

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

This does not seem const-correct to me.

Is there a way to prevent this behavior while still maintaining the ability to use the copy constructor? In my real use case, I still want Obj std containers to be possible.

+7
source share
6 answers

"It doesn't seem const-right to me," but it does: you just call the non const get method on non-const Obj . Nothing wrong with that.

If you really need the behavior you need, you can use something like a const Obj proxy, but then your clients should be able to handle it, of course:

 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 
+2
source

No, no, if you do not want to store shared_ptr<const int> , in which case no one can access it as not const.

+1
source

This does not violate const. The integer object val points to is a separate object that does not belong exclusively to the original object. Changing its value does not affect the state of Obj objects.

+1
source

Is there a way to prevent this behavior while still maintaining the ability to use the copy constructor? In my real use case, I still want Obj std containers to be possible.

You can specify a different copy constructor to copy from the const object - this means that you can, for example, avoid copying the common pointer and instead create a non-constant object using the NULL pointer, or you can make a deep copy of the pointer number. I would be very wary of doing such things: it’s strange to have different behavior depending on the constant of the copied variable - I’m afraid that it will complicate the reasonable behavior of your program. But you have to choose some behavior or accept the current behavior, since std::vector<> will create copies sometimes - you cannot just leave it undefined.

+1
source

Manually create the Obj copy constructor, which then must copy the contents of the shared pointer. This avoids changing the contents of const1 through nonconst3 , since they point to different instances of int.

However, you want to avoid deep copies of non-const instances of Obj (where this is not a problem and is intended to reuse the old common pointer). To do this, you need to provide both const constructors and not const const and copy only to const one:

 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 //... } 
0
source

No, no ... But you can use the COW pointer, deep-copy , when you can write value (in a non-constant getter).

Or you can write two copy-ctors (for ref make a shallow copy, for cref make a deep copy).

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

All Articles