tangobravo
tangobravo

Reputation: 926

Const correctness 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 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

Answers (6)

stijn
stijn

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

Tony Delroy
Tony Delroy

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

leemes
leemes

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

ForEveR
ForEveR

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

Kos
Kos

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

Puppy
Puppy

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

Related Questions