Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Implementation of shared_ptr

155 views
Skip to first unread message

Minkoo Seo

unread,
Aug 30, 2006, 8:41:00 PM8/30/06
to
Hi group!

I've got a question on the implementation of std::tr1::shared_ptr. In
the book titled Beyond C++ Standard Library, a simple technique to
prevent the deletion of raw pointer of shared_ptr is presented:

#include <iostream>
#include <tr1/memory>

using namespace std;
using namespace std::tr1;

class A
{
protected:
virtual ~A() { }
};

class B: public A
{
public:
virtual ~B() { }
};

int main()
{
shared_ptr<A> a(new B());
A *raw_a = a.get();
delete raw_a;

return EXIT_SUCCESS;
}

In this example, delete raw_a raises a compile time error because A's
destructor is protected. However, the following successfully compiles
and run:

int main()
{
shared_ptr<A> a(new B());
return EXIT_SUCCESS;
}

This means that, in shared_ptr, something like the following is
happening:

B *b = dynamic_cast<B*>(a.get());
delete b;

I searched for the source code, but I could not even find the
destructor. Moreover, AFAIK, shared_ptr is implemented like:

template<typename T>
class shared_ptr
{
T *t_;

public:
shared_ptr(T *t): t_(t) { }

template<typename Y>
shared_ptr(Y *y): t_(y) { }
...
};

The second construtor, which takes Y *y as an argument, is provided for
the situation where type conversion can happen like the one I've shown
above (storing new B() into shared_ptr<A>). And this implies that the
type information of Y is completely lost after y is assigned to t_.
That being the case, shared_ptr<A> a(new B()) can not deallocate the
memory because the deletition of a raw pointer of type A is a compile
time error.

So, I'm curious how shared_ptr is handling the deallocation of Y *y.
Could anybody give me some pointers?

Sincerely,
Minkoo Seo


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Ulrich Eckhardt

unread,
Aug 31, 2006, 11:00:29 AM8/31/06
to
Minkoo Seo wrote:
> I've got a question on the implementation of std::tr1::shared_ptr. In
> the book titled Beyond C++ Standard Library, a simple technique to
> prevent the deletion of raw pointer of shared_ptr is presented:
[...]
> class A [virtual protected dtor]
> class B: public A [virtual public dtor]
[...]

> shared_ptr<A> a(new B());
> A *raw_a = a.get();
> delete raw_a;

> In this example, delete raw_a raises a compile time error because A's


> destructor is protected. However, the following successfully compiles
> and run:
>
> int main()
> {
> shared_ptr<A> a(new B());
> return EXIT_SUCCESS;
> }
>
> This means that, in shared_ptr, something like the following is
> happening:
>
> B *b = dynamic_cast<B*>(a.get());
> delete b;

No, it's slightly more complicated. The thing is also hinted at in the
documentation (well, at least in the origin at Boost's): shared_ptr<> holds
a deleter object that knows the type passed to the constructor. That means
that even if you
shared_ptr<void> p(new B);
it will remember that it points to a B and invoke delete on the B pointer
passed to it. The thing that would happen in a primitive smart pointer is
to call delete on a void pointer, invoking undefined behaviour.

> I searched for the source code, but I could not even find the
> destructor. Moreover, AFAIK, shared_ptr is implemented like:
>
> template<typename T>
> class shared_ptr
> {
> T *t_;
>
> public:
> shared_ptr(T *t): t_(t) { }
>
> template<typename Y>
> shared_ptr(Y *y): t_(y) { }
> ...
> };
>
> The second construtor, which takes Y *y as an argument, is provided for
> the situation where type conversion can happen like the one I've shown
> above (storing new B() into shared_ptr<A>). And this implies that the
> type information of Y is completely lost after y is assigned to t_.

No, your assumption about the implementation is wrong. The shared_ptr holds
not only a pointer to the object, but also a pointer to a reference counter
and a functor for deleting the contained object.

If you take a look at a shared ptr object in a debugger, its internal
structure should become a bit clearer, at least clear enough to then find
the relevant codeparts. Also be sure to look at the Boost documentation, it
contains quite a few hints about how shared_ptr can be used - it is much
more complex than it looks on first sight.

Uli

Pete Becker

unread,
Aug 31, 2006, 3:10:18 PM8/31/06
to
Minkoo Seo wrote:

>
> I've got a question on the implementation of std::tr1::shared_ptr. In
> the book titled Beyond C++ Standard Library, a simple technique to
> prevent the deletion of raw pointer of shared_ptr is presented:
>

That's a bit misleading. Making the base type's destructor protected
prevents deleting an object through a pointer to the base type,
regardless of whether that pointer came from a shared_ptr object.

>
> [example elided]

>
> In this example, delete raw_a raises a compile time error because A's
> destructor is protected. However, the following successfully compiles
> and run:
>
> int main()
> {
> shared_ptr<A> a(new B());
> return EXIT_SUCCESS;
> }
>
> This means that, in shared_ptr, something like the following is
> happening:
>

As Ulrich said, the shared_ptr object internally deals with a B*, since
that's what was passed to its constructor. It knows how to delete it.
This is discussed in section 2.9 of my book, "The C++ Standard Library
Extensions: a Tutorial and Reference."

iwongu

unread,
Sep 5, 2006, 8:26:49 AM9/5/06
to

Minkoo Seo 작성:

Here is the simplest version of shared_ptr implementation I can think
of.

I omitted many important methods because it's not important in this
question.
It is different from tr1::shared_ptr implementation. The
tr1::shared_ptr has more layers, more methods, broken compiler
considerations, deleter object, ... In short, it's far more complex.
But you can see some internal mechanism of shared_ptr from this code.

The smart_ptr should keep type inforamtion to delete correctly. So it
newed the 'impl<T>' instance using impl_base pointer. When smart_ptr is
deleted, it calls the virtual destructor of impl_base. And finally, it
deletes the impl::U* not smart_ptr::T*.

struct impl_base
{
impl_base(int c) : cnt(c) { }
virtual ~impl_base() { }

int cnt;
};

template <class U>
struct impl : impl_base
{
impl(U* p) : impl_base(1), ptr(p) { }

~impl() {
delete ptr;
}

U* ptr;
};

template <class T>
class smart_ptr
{
public:
smart_ptr(T *p) : impl_(new impl<T>(p)), ptr_(p) { }

template<typename Y>
smart_ptr(Y *p) : impl_(new impl<Y>(p)), ptr_(p) { }

~smart_ptr() {
--impl_->cnt;
if (impl_->cnt == 0) delete impl_;
}

T* get() const {
return ptr_;
}

private:
impl_base* impl_;
T* ptr_;
};

HTH,

iwongu

0 new messages