4

I am reading this,
http://www.informit.com/articles/article.aspx?p=31529&seqNum=5
and author explain three types of smart pointer design (see pictures at the end of the post).

I believe current GCC, CLang and probably Visual C++ uses smart pointers with control block.

I can imagine why intrusive reference counting is not used, but what is the problem with second implementation - smart pointer with pointer to pointer block? There should be two pointer de-references, but smart pointer object size will be just half.

smart pointer with control block

smart pointer with control block

smart pointer with pointer to pointer block

smart pointer with pointer to pointer block

smart pointer with intrusive reference counting

enter image description here

8
  • Instead of believing, why don't you read the code and see what the implementations actually do? Commented Aug 7, 2015 at 9:47
  • 4
    When using make_shared, many implementations implement a memory layout like shown above for intrusive reference counting. This has the benfit of using only a single allocation but the drawback that a sole living weak_ptr will keep the whole memory allocated even no smart_ptr to its object exists anymore. See e.g. Scott Meyers, More Effective C++. Commented Aug 7, 2015 at 10:52
  • 2
    std::unique_ptr doesn't perform any extra heap allocations, while make_shared does. So of course the memory layout will be different. If you mean auto up = std::shared_ptr(ptr); - then yes, it is a common implementation technique to have make_shared combine memory for the object itself, and for the control block, together into a single heap allocation, while shared_ptr's constructor obviously can't do that (the allocation for the object has already happened). Commented Aug 7, 2015 at 14:13
  • 1
    Double dereferences are expensive, and so are fat pointers. Each could be better in some circumstances but not in others. Implementations have to select one technique, so I like to think that the selection is done after a careful consideration of all things and that the selected variant will provide the best solution for most people most of the time. In reality, who knows. Commented Aug 9, 2015 at 20:26
  • 2
    "smart pointer with control block" uses slightly more memory, but allows faster dereferencing of the smart pointer. With a pointer to pointer block you need an additional memory load of pObj_ from the SPimpl object before you can get the address of your real object. Commented Aug 9, 2015 at 20:30

1 Answer 1

4

One important reason is performance, shared_ptr::get() doesn't have to dereference a pointer to find the object address if it's stored directly inside the shared_ptr object.

But apart from performance, the smart pointer with pointer to pointer block implementation wouldn't support all the things you can do with shared_ptr e.g.

std::shared_ptr<int> pi(new int(0));
std::shared_ptr<void> pv = pi;
std::shared_ptr<int> pi2 = static_pointer_cast<int>(pv);

struct A {
  int i;
};
std::shared_ptr<A> pa(new A);
std::shared_ptr<int> pai(pa, pa->i);

struct B { virtual ~B() = default; };
struct C : B { };
std::shared_ptr<B> pb(new C);
std::shared_ptr<C> pc = std::dynamic_pointer_cast<C>(pb);

In these examples pv, pai and pb store a pointed that is not the same type as the pointer owned by the control block, so there must be a second pointer (which might be a different type) stored in the shared_ptr itself.

For pv and pb it would be possible to make it work, by converting the pointer stored in the control block to the type that needs to be returned. That would work in some cases, although there are examples using multiple inheritance that would not work correctly.

But for the pai example (which uses the aliasing constructor) there is no way to make that work without storing a pointer separate to the one in the control block, because the two pointers are completely unrelated types and you can't convert between them.

You said in a comment:

I see and in case of make_shared, second pointer points to the address internal to the allocated block. (I actually tried this already and it seems that way)

Yes, that's correct. There is still a second pointer, but both poitners refer into the same block of memory. This has the advantage that only one memory allocation is needed instead of two separate ones for the object and the control block. Additionally, the object and control block are adjacent in memory so are more likely to share a cache line. If the CPU has got the ref-count in its cache already then it probably also has the object in its cache, so accessing them both is faster and means there is another cache line available to be used for other data.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.