How Is the Std::Tr1::Shared_Ptr Implemented

How is the std::tr1::shared_ptr implemented?

shared_ptr must manage a reference counter and the carrying of a deleter functor that is deduced by the type of the object given at initialization.

The shared_ptr class typically hosts two members: a T* (that is returned by operator-> and dereferenced in operator*) and a aux* where aux is a inner abstract class that contains:

  • a counter (incremented / decremented upon copy-assign / destroy)
  • whatever is needed to make increment / decrement atomic (not needed if specific platform atomic INC/DEC is available)
  • an abstract virtual destroy()=0;
  • a virtual destructor.

Such aux class (the actual name depends on the implementation) is derived by a family of templatized classes (parametrized on the type given by the explicit constructor, say U derived from T), that add:

  • a pointer to the object (same as T*, but with the actual type: this is needed to properly manage all the cases of T being a base for whatever U having multiple T in the derivation hierarchy)
  • a copy of the deletor object given as deletion policy to the explicit constructor (or the default deletor just doing delete p, where p is the U* above)
  • the override of the destroy method, calling the deleter functor.

A simplified sketch can be this one:

template<class T>
class shared_ptr
{
struct aux
{
unsigned count;

aux() :count(1) {}
virtual void destroy()=0;
virtual ~aux() {} //must be polymorphic
};

template<class U, class Deleter>
struct auximpl: public aux
{
U* p;
Deleter d;

auximpl(U* pu, Deleter x) :p(pu), d(x) {}
virtual void destroy() { d(p); }
};

template<class U>
struct default_deleter
{
void operator()(U* p) const { delete p; }
};

aux* pa;
T* pt;

void inc() { if(pa) interlocked_inc(pa->count); }

void dec()
{
if(pa && !interlocked_dec(pa->count))
{ pa->destroy(); delete pa; }
}

public:

shared_ptr() :pa(), pt() {}

template<class U, class Deleter>
shared_ptr(U* pu, Deleter d) :pa(new auximpl<U,Deleter>(pu,d)), pt(pu) {}

template<class U>
explicit shared_ptr(U* pu) :pa(new auximpl<U,default_deleter<U> >(pu,default_deleter<U>())), pt(pu) {}

shared_ptr(const shared_ptr& s) :pa(s.pa), pt(s.pt) { inc(); }

template<class U>
shared_ptr(const shared_ptr<U>& s) :pa(s.pa), pt(s.pt) { inc(); }

~shared_ptr() { dec(); }

shared_ptr& operator=(const shared_ptr& s)
{
if(this!=&s)
{
dec();
pa = s.pa; pt=s.pt;
inc();
}
return *this;
}

T* operator->() const { return pt; }
T& operator*() const { return *pt; }
};

Where weak_ptr interoperability is required a second counter (weak_count) is required in aux (will be incremented / decremented by weak_ptr), and delete pa must happen only when both the counters reach zero.

shared_ptr in std::tr1

In G++ 4.3,

#include <tr1/memory>

should do the trick. You'll find shared_ptr at std::tr1::shared_ptr.

using std::tr1::shared_ptr as an internal mechanism for reference counting

Consider instead using a shared_ptr<FILE> with a custom deleter:

struct fclose_deleter
{
void operator()(FILE* f)
{
if (f)
{
std::fclose(f);
}
}
};

Then, your File class is much simpler (and correcter):

class File
{
public:
File(const char* path, const char* mode)
: _file(std::fopen(path, mode), fclose_deleter())
{
}

int write(void const* buff, size_t size)
{
// You'll want to verify that _file.get() is valid, or you'll want to
// throw in the constructor if the call to 'std::fopen()' fails.
std::fwrite(buff, size, 1, _file.get());
}

private:
std::tr1::shared_ptr<FILE> _file;
};

Casting std::tr1::shared_ptrT and std::shared_ptrT with same function but different overloads

Make your Cast function template take a template template parameter:

template<typename T, template<class> class SP, class U>
SP<T> Cast2(SP<U> const& sp) {
using std::dynamic_pointer_cast;
using std::tr1::dynamic_pointer_cast;
return dynamic_pointer_cast<T>(sp);
}

demo


Leaving the original answer for posterity. It is ill-formed on VC++ (though it works as expected), because there is no valid specialization of the function.

Disable the second overload if std::shared_ptr and std::tr1::shared_ptr are the same thing (they are on VC++ 10, they are not for my gcc).

template<class T, class U>
typename std::enable_if<
!std::is_same< std::shared_ptr<T>, std::tr1::shared_ptr<T> >::value,
std::tr1::shared_ptr<T>
>::type
Cast( const std::tr1::shared_ptr<U>& spObject ) // rename from CastTerrainObject
{
return std::tr1::dynamic_pointer_cast<T>(spObject);
}

The following compiles on both VC++ 10 and the latest gcc. Unfortunately, it's ill-formed on VC++10 (despite working as expected)

#include <memory>
#include <type_traits>
#ifndef _WIN32
#include <tr1/type_traits>
#include <tr1/shared_ptr.h>
#endif

template<class T, class U> // rename from CastTerrainObject
std::shared_ptr<T> Cast( const std::shared_ptr<U>& spObject )
{
return std::dynamic_pointer_cast<T>(spObject);
}

template<class T, class U>
typename std::enable_if<
!std::is_same< std::shared_ptr<T>, std::tr1::shared_ptr<T> >::value,
std::tr1::shared_ptr<T>
>::type
Cast( const std::tr1::shared_ptr<U>& spObject ) // rename from CastTerrainObject
{
return std::tr1::dynamic_pointer_cast<T>(spObject);
}

struct B{ virtual ~B(){} };
struct D:B{};

int main()
{
Cast<B>(std::make_shared<D>());
}

demo

You could also ifdef the second overload away, but I'm not sure which conditions should be checked.

Implementing std::equal with tr1::shared_ptr types

Easiest may be to use find_if:

template <typename T>
struct shared_ptr_finder
{
T const & t;

shared_ptr_finder(T const & t_) : t(t_) { }

bool operator()(std::tr1::shared_ptr<T> const & p)
{
return *p == t;
}
};

template <typename T>
shared_ptr_finder<T> find_shared(std::tr1::shared_ptr<T> const & p)
{
return shared_ptr_finder<T>(*p);
}

#include <algorithm>

typedef std::vector< std::tr1::shared_ptr<Color> >::iterator it_type;
it_type it1 = std::find_if(myVector.begin(), myVector.end(), find_shared(p2));
it_type it2 = std::find_if(myVector.begin(), myVector.end(), shared_ptr_finder<Color>(*p2));

Is it legal to place using tr1::shared_ptr in namespace std in header?

Technically, the Standard says that you enter the realm of Undefined Behavior if you do this:

17.6.4.2.1 Namespace std [namespace.std]

1 The behavior of a C++ program is undefined if it adds declarations or
definitions to namespace std or to a namespace within namespace std
unless otherwise specified.

But in practice, you are likely to get away with it. Heck, even Scott Meyers proposed a similarly undefined namespace alias trick in Effective C++ 3rd Ed. (Item 54, p.268) to use Boost functionality as a stopgap for missing tr1 functionality.

namespace std { using namespace tr1 = ::boost; }

Your using declaration is also undefined behavior, but go ahead and jump right in.

NOTE: comment it with a big fat warning, #define and #pragma around your compiler version and warnings, and as soon as you upgrade to a compiler/library that actually has std::shared_ptr, make sure to revisit that header and remove the code.

Any hit for dereferencing std::tr1:shared_ptr vs. dereferencing a naked pointer?

In an optimized build without debugging support, there shouldn't be any overhead. You can find out by taking a look at the implementation you are using. Chances are, its operator-> overload just returns the pointer to the pointed-to object and its operator* overload just dereferences this pointer.

(This is what the Visual C++ 2010 implementation of std::shared_ptr does: each of those overloaded operators just calls a "get" function which just returns the pointer; there is no locking or other overhead of any kind. Other implementations may be different.)

An unoptimized build may not inline the operator overload and if your implementation has extra debugging support that you enable, it may perform extra checks (e.g., perhaps an assert if you dereference a null pointer).

A std::tr1::shared_ptr for Objective C++ on iPhone?

As long as you learn the memory management rules first, there is no real problem with shared_ptr - it can help you in C++ contexts but doesn't let the ownership questions magically disappear.

shared_ptr supports a custom deallocator so the following:

@interface A : NSObject
- (void)f;
@end

@implementation A
- (void)dealloc { NSLog(@"bye"); [super dealloc]; }
- (void)f { NSLog(@"moo"); }
@end

void my_dealloc(id p) {
[p release];
}

// ...
{
shared_ptr<A> p([[A alloc] init], my_dealloc);
[p.get() f];
}

... outputs:

moo

bye

... as expected.

If you want you can hide the deallocator from the user using a helper function, e.g.:

template<class T> shared_ptr<T> make_objc_ptr(T* t) {
return shared_ptr<T>(t, my_dealloc);
}

shared_ptr<A> p = make_objc_ptr<A>([[A alloc] init]);


Related Topics



Leave a reply



Submit