Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does copying a const shared_ptr& not violate const-ness?

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)

like image 494
Thomas Avatar asked Mar 28 '16 21:03

Thomas


People also ask

What is the difference between ::ptr and ::constptr?

::Ptr is typedef-ed to std::shared_ptr<MSG>, which is a shared pointer to a message, and ::ConstPtr is typedef-ed to std::shared_ptr<MSG const>, which is a shared pointer to a const message. but what does & means? If msg is already a pointer, why do we take the address of msg? Or does that & means passing by reference?

Should shared_ptr<const T> be orthogonal?

Why not be orthogonal, and put the const after everywhere (since you have to put it after in some cases). You are right. shared_ptr<const T> p; is similar to const T * p; (or, equivalently, T const * p; ), that is, the pointed object is const whereas const shared_ptr<T> p; is similar to T* const p; which means that p is const.

How to add a copy constructor to a smart pointer?

You can add copy constructor by Person (const Person&) = default; It seems that you want to have 'deep copy' rather than a 'shared' object. The smart-pointer acts like the raw-pointer, so the shared resource would change if any change is made through one of the pointer pointing to it.

How do you pass a message to a shared_ptr object?

The shared_ptr object is a class which manages the object it wraps and "points to", and the & is to pass the shared_ptr object by reference. So, we are passing a message by a reference to a smart pointer which points to it--yeah, it's kind of a double-layered approach.


Video Answer


5 Answers

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:

  • the private members that implement shared ownership semantics might be marked mutable
  • the data structures that implement shared ownership might not actually live in the shared_ptr object - they might be a separate set of objects that might be obtained through a pointer, for example.
like image 93
Michael Burr Avatar answered Oct 13 '22 21:10

Michael Burr


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.

like image 35
Richard Hodges Avatar answered Oct 13 '22 20:10

Richard Hodges


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

like image 39
nasser-sh Avatar answered Oct 13 '22 21:10

nasser-sh


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...

like image 2
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 21:10

Yakk - Adam Nevraumont


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":

  1. 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.

  2. 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.

like image 1
Edward Strange Avatar answered Oct 13 '22 22:10

Edward Strange