2

I have some confusion about the shared_ptr copy constructor. Please consider the following 2 lines:

  1. It is a "constant" reference to a shared_ptr object, that is passed to the copy constructor so that another shared_ptr object is initialized.

  2. The copy constructor is supposed to also increment a member data - "reference counter" - which is also shared among all shared_ptr objects, due to the fact that it is a reference/pointer to some integer telling each shared_ptr object how many of them are still alive.

But, if the copy constructor attempts to increment the reference counting member data, does it not "hit" the const-ness of the shared_ptr passed by reference? Or, does the copy constructor internally use the const_cast operator to temporarily remove the const-ness of the argument?

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
softwarelover
  • 1,009
  • 1
  • 10
  • 22

5 Answers5

5

The phenomenon you're experiencing is not special to the shared pointer. Here's a typical primeval example:

struct Foo
{
    int * p;
    Foo() : p(new int(1)) { }
};

void f(Foo const & x)  // <-- const...?!?
{
    *x.p = 12;         // ...but this is fine!
}

It is true that x.p has type int * const inside f, but it is not an int const * const! In other words, you cannot change x.p, but you can change *x.p.

This is essentially what's going on in the shared pointer copy constructor (where *p takes the role of the reference counter).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
3

Although the other answers are correct, it may not be immediately apparent how they apply. What we have is something like this:

template <class T>
struct shared_ptr_internal {
    T *data;
    size_t refs;
};

template <class T>
class shared_ptr {
    shared_ptr_internal<T> *ptr;
public:
    shared_ptr(shared_ptr const &p) { 
        ptr = p->ptr;
        ++(ptr->refs);
    }
    // ...
};

The important point here is that the shared_ptr just contains a pointer to the structure that contains the reference count. The fact that the shared_ptr itself is const doesn't affect the object it points at (what I've called shared_ptr_internal). As such, even when/if the shared_ptr itself is const, manipulating the reference count isn't a problem (and doesn't require a const_cast or mutable either).

I should probably add that in reality, you'd probably structure the code a bit differently than this -- in particular, you'd normally put more (all?) of the code to manipulate the reference count into the shared_ptr_internal (or whatever you decide to call it) itself, instead of messing with those in the parent shared_ptr class.

You'll also typically support weak_ptrs. To do this, you have a second reference count for the number of weak_ptrs that point to the same shared_ptr_internal object. You destroy the final pointee object when the shared_ptr reference count goes to 0, but only destroy the shared_ptr_internal object when both the shared_ptr and weak_ptr reference counts go to 0.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
2

It uses an internal pointer which doesn't inherit the contests of the argument, like:

(*const_ref.member)++;

Is valid.

Daniel
  • 30,896
  • 18
  • 85
  • 139
0

the pointer is constant, but not the value pointed to.

Walter
  • 44,150
  • 20
  • 113
  • 196
0

Wow, what an eye opener this has all been! Thanks to everyone that I have been able to pin down the source of confusion to the fact that I always assumed the following ("a" contains the address of "b") were all equivalent.

int const  *a = &b;    // option1
const int  *a = &b;    // option2
int * const a = &b;    // option3

But I was wrong! Only the first two options are equivalent. The third is totally different.

With option1 or option2, "a" can point to anything it wants but cannot change the contents of what it points to.

With option3, once decided what "a" points to, it cannot point to anything else. But it is free to change the contents of what it is pointing to. So, it makes sense that shared_ptr uses option3.

softwarelover
  • 1,009
  • 1
  • 10
  • 22