Reputation: 926
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 constructed and copies are made they can all modify the same value through the shared_ptr exposed by Obj.
Obj nonconst1;
Obj nonconst2(nonconst1);
nonconst2.get() = 2;
cout << nonconst1.get() << ", " << nonconst2.get() << endl;
It's also possible to copy-construct a const Obj
object from one of the non const, which seems to do the correct thing in that it allows reading but not writing to the value - as expected the following code results in a compile error:
const Obj const1(nonconst1);
const1.get() = 3;
However it is possible to copy-construct a non-const Obj from the const one, which then does permit the value to be modified.
Obj nonconst3(const1);
nonconst3.get() = 3;
To me this doesn't feel const-correct.
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.
Upvotes: 7
Views: 1217
Reputation: 35901
"To me this doesn't feel const-correct" yet it is: you're simply invoking a non const get
method on a non const Obj
. Nothing wrong with that.
If you really need the behaviour you're after, you could to use something like a const proxy to Obj
but then your clients must 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
Upvotes: 2
Reputation: 106096
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.
You could specify a different copy constructor for copying from a const
object - that means you can e.g. avoid copying the shared pointer and instead create the non-const object with a NULL pointer, or you could do a deep-copy of the pointed-to number. I'd be pretty wary of doing this kind of thing though - it's weird to get different behaviours depending on the constness of the copied variable - I fear it'd make it very hard to reason about your program behaviour. But, you must choose some behaviour or accept the current behaviour as std::vector<>
will create copies sometimes - you can't simply leave it undefined.
Upvotes: 1
Reputation: 45665
Manually implement a copy constructor of Obj
which should then copy the shared pointer's content. This avoids modifying the content of const1
via nonconst3
, since they point to different int instances.
However, you want to avoid deep copies of non-const instances of Obj
(where it's no problem and intended to reuse an old shared pointer). For this, you have to provide both const and non-const copy constructors and copy only in the 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
//...
}
Upvotes: 0
Reputation: 55887
No, there isn't... But you can use COW
, deep-copy
pointer, when you can write to value
(in non-const getter).
Or, you can write two copy-ctors
(for ref
do shallow copy, for cref
do deep copy).
A(A& obj) : pointer(obj.pointer) {}
A(const A& obj) : pointer(new int(*obj.pointer)) {}
Upvotes: 0
Reputation: 72241
That doesn't break const correctness. The integer object pointed to by val
is a distinct object, which isn't exclusively owned by the original object. Modifying its value doesn't affect the state of Obj
objects.
Upvotes: 1
Reputation: 146910
No, there isn't, unless you want to store a shared_ptr<const int>
, in which case nobody can access it as non-const.
Upvotes: 1