Reputation: 5104
Even though my code compiles fine, this is something that has been bugging me, and I wasn't able to find an answer on stackoverflow. The following generic constructor is one way to pass a shared_ptr to a class instance in the constructor.
MyClass {
MyClass(const std::shared_ptr<const T>& pt);
std::shared_ptr<const T> pt_; //EDITED: Removed & typo
};
MyClass::MyClass(const std::shared_ptr<const T>& pt)
: pt_(pt)
{ }
This compiles fine. My question is the following: In my understanding, declaring a parameter const like this:
void myfunc(const T& t)
promises not to change t. However, by copying the shared_ptr pt to pt_, am I not effectively increasing the use count of the shared_ptr pt, thereby violating the supposed const-ness?
This might be a fundamental misunderstanding of shared_ptrs on my side?
(For anybody reading this looking to implement it, note that this might be a better implementation)
Upvotes: 13
Views: 3111
Reputation: 69864
A shared pointer is conceptually laid out as follows:
The shared_ptr contains a pointer to the object and also a pointer to a control block. It is the control block that controls the lifetime of the pointee, not the shared_ptr object itself, which is no more than a wrapper and some code to notify the control block that the number of references has been increased or reduced. It is the control block that stores the reference count, the deleter and the pointer to the original interface of the pointee (so the deleter can delete against the correct interface even if there has been a pointer cast).
* shared_ptr object *
| pointer to object | ---------------> object
| pointer to control block |----+ +> (possibly different interface
| | but still same object)
| |
* control block * <----------+ |
| reference count | |
| deleter | |
| pointer to object | --------------+
Since a shared_ptr's memory looks something like this:
template<class T>
struct shared_ptr {
T* ptr;
control_block* pctrl;
};
It should start to become obvious that even if the shared_ptr is const, taking a copy does not require any mutation of the shared_ptr's internal state. The mutation happens in the control block, which is pointed to by the shared_ptr.
Thus the contract is not broken. Just as if you declared
T* const p;
modifying p
itself is not possible, but modifying (*p)
is perfectly reasonable.
Upvotes: 5
Reputation: 275385
const
, in an interface, means whatever it wants to mean. Its meaning should be documented.
Usually, it means "some subset of ny state will not change". For shared ptr, the subset of state that cannot change is "what I point to".
The count can change. The contents can change.
In the C++ std library, const
can be interpreted to mean "thread safe" -- because if const
operations are thread safe, and you put them in std
containers, the std
container in turn has thread-safe const operations.
By thread safe, I do not mean sync -- I mean two different threads, both doing const stuff, is a-ok. If a thread is doing non-const stuff, all bets are off.
This permits simple reader-writer lock logic.
And as add/remove ref is indeed thread safe, while reseating ptr is not...
Upvotes: 2
Reputation: 3653
The simple answer to your question is no, because the reference count is not stored in the shared pointer instance, but in an external object which takes care of keeping the reference count. When you copy construct a shared_ptr, the reference count is added in the external object. Check out this lecture by Stephen T. Lavavej which explains the implementation
Upvotes: 3
Reputation: 15468
Passing a shared_ptr
as reference does not increase its reference count.Moreover, you are not copying anything here, you just take references, so the reference count remains unchanged.
Note that using a reference to a shared-pointer as class member is usually not the thing you've wanted. By this, you get no guarantee that the pointer is still alive when you want to use it -- which is basically the goal when using a shared-pointer.
Response to your edit: Now, by using a shared-pointer member, you indeed create a copy and thus increase the ref-count. This is possible, as you can copy const objects. So this guaraantee you mentioned does not exists -- essentially, the keyword mutable
prevents from it.
Upvotes: 1
Reputation: 340208
One of the members that std::shared_prt<>
must have is the old fashioned copy constructor:
shared_ptr(const shared_ptr& r) noexcept;
The standard says (C++11 20.7.2.2.1/18 "shared_ptr constructors") that "if r is empty, constructs an empty shared_ptr object; otherwise, constructs a shared_ptr object that shares ownership with r".
The standard doesn't make mention of how "shares ownership with r" might be accomplished though a const
reference. Some options might be:
mutable
shared_ptr
object - they might be a separate set of objects that might be obtained through a pointer, for example.Upvotes: 9
Reputation: 40859
My question is the following: In my understanding, declaring a parameter const like this...promises not to change t.
Not entirely true. The promise is not to change any of its observable state...most of the time. There are several ways in which a const object can "change":
It has mutable variables -- these are meant to change under const constraints but design methodology says these should be rare and shouldn't be observable. One more common use for them is to cache something that is expensive to calculate. So you have a const function get
that does a massive calculation to return a value--you want this to be optimized so you create a cache. The cache has to change during the get
call but in reality get
always returns the same thing so nobody could observe that the state of the object has changed.
It has non-const pointers or references to other objects. In these cases, aggregation, it's not the object that changes but something else. This is what happens in the case of shared_ptr
, which has a pointer to a shared reference counting object that actually holds the value of the pointer. It's unintuitive at first because the reported state of such an object can change, but it's actually not the object itself that changed. Design methodology here is on a case-by-case basis, but the language in no way protects you unless you declare the pointers as pointer to const.
Upvotes: 1