What is the best way to implement smart pointers in C++?
Asked Answered
S

9

12

I've been evaluating various smart pointer implementations (wow, there are a LOT out there) and it seems to me that most of them can be categorized into two broad classifications:

1) This category uses inheritance on the objects referenced so that they have reference counts and usually up() and down() (or their equivalents) implemented. IE, to use the smart pointer, the objects you're pointing at must inherit from some class the ref implementation provides.

2) This category uses a secondary object to hold the reference counts. For example, instead of pointing the smart pointer right at an object, it actually points at this meta data object... Who has a reference count and up() and down() implementations (and who usually provides a mechanism for the pointer to get at the actual object being pointed to, so that the smart pointer can properly implement operator ->()).

Now, 1 has the downside that it forces all of the objects you'd like to reference count to inherit from a common ancestor, and this means that you cannot use this to reference count objects that you don't have control over the source code to.

2 has the problem that since the count is stored in another object, if you ever have a situation that a pointer to an existing reference counted object is being converted into a reference, you probably have a bug (I.E., since the count is not in the actual object, there is no way for the new reference to get the count... ref to ref copy construction or assignment is fine, because they can share the count object, but if you ever have to convert from a pointer, you're totally hosed)...

Now, as I understand it, boost::shared_pointer uses mechanism 2, or something like it... That said, I can't quite make up my mind which is worse! I have only ever used mechanism 1, in production code... Does anyone have experience with both styles? Or perhaps there is another way thats better than both of these?

Sleep answered 2/2, 2009 at 16:33 Comment(0)
S
26

"What is the best way to implement smart pointers in C++"

  1. Don't! Use an existing, well tested smart pointer, such as boost::shared_ptr or std::tr1::shared_ptr (std::unique_ptr and std::shared_ptr with C++ 11)
  2. If you have to, then remember to:
    1. use safe-bool idiom
    2. provide an operator->
    3. provide the strong exception guarantee
    4. document the exception requirements your class makes on the deleter
    5. use copy-modify-swap where possible to implement the strong exception guarantee
    6. document whether you handle multithreading correctly
    7. write extensive unit tests
    8. implement conversion-to-base in such a way that it will delete on the derived pointer type (policied smart pointers / dynamic deleter smart pointers)
    9. support getting access to raw pointer
    10. consider cost/benifit of providing weak pointers to break cycles
    11. provide appropriate casting operators for your smart pointers
    12. make your constructor templated to handle constructing base pointer from derived.

And don't forget anything I may have forgotten in the above incomplete list.

Seem answered 2/2, 2009 at 19:0 Comment(0)
C
9

Just to supply a different view to the ubiquitous Boost answer (even though it is the right answer for many uses), take a look at Loki's implementation of smart pointers. For a discourse on the design philosophy, the original creator of Loki wrote the book Modern C++ Design.

Clougher answered 2/2, 2009 at 17:13 Comment(1)
+1 since boost do not provide deep_copy option for it's smart pointers and I think that's a shame.Nonmaterial
N
7

I've been using boost::shared_ptr for several years now and while you are right about the downside (no assignment via pointer possible), I think it was definitely worth it because of the huge amount of pointer-related bugs it saved me from.

In my homebrew game engine I've replaced normal pointers with shared_ptr as much as possible. The performance hit this causes is actually not so bad if you are calling most functions by reference so that the compiler does not have to create too many temporary shared_ptr instances.

Noggin answered 2/2, 2009 at 16:44 Comment(2)
In addition, the boost version of shared_ptr has migrated into TR1 and so will eventually be standard C++ library.Mccrory
A performance comparison of boost smart pointers is here: boost.org/doc/libs/1_39_0/libs/smart_ptr/smarttests.htmRomine
H
3

Boost also has an intrusive pointer (like solution 1), that doesn't require inheriting from anything. It does require changing the pointer to class to store the reference count and provide appropriate member functions. I've used this in cases where memory efficiency was important, and didn't want the overhead of another object for each shared pointer used.

Example:

class Event {
public:
typedef boost::intrusive_ptr<Event> Ptr;
void addRef();
unsigned release();
\\ ...
private:
unsigned fRefCount;
};

inline void Event::addRef()
{
  fRefCount++;
}
inline unsigned Event::release(){
    fRefCount--;
    return fRefCount;
}

inline void intrusive_ptr_add_ref(Event* e)
{
  e->addRef();
}

inline void intrusive_ptr_release(Event* e)
{
  if (e->release() == 0)
  delete e;
}

The Ptr typedef is used so that I can easily switcth between boost::shared_ptr<> and boost::intrusive_ptr<> without changing any client code

Heelandtoe answered 2/2, 2009 at 16:55 Comment(0)
K
3

If you stick with the ones that are in the standard library you will be fine.
Though there are a few other types than the ones you specified.

  • Shared: Where the ownership is shared between multiple objects
  • Owned: Where one object owns the object but transfer is allowed.
  • Unmovable: Where one object owns the object and it can not be transferred.

The standard library has:

  • std::auto_ptr

Boost has a couple more than have been adapted by tr1 (next version of the standard)

  • std::tr1::shared_ptr
  • std::tr1::weak_ptr

And those still in boost (which in relatively is a must have anyway) that hopefully make it into tr2.

  • boost::scoped_ptr
  • boost::scoped_array
  • boost::shared_array
  • boost::intrusive_ptr

See: Smart Pointers: Or who owns you baby?

Kerwon answered 2/2, 2009 at 17:3 Comment(1)
I don't believe that boost::scoped_ptr made it into tr1, so it's still boost::scoped_ptr, not std::tr1::scoped_ptr.Sanitarium
S
2

It seems to me this question is kind of like asking "Which is the best sort algorithm?" There is no one answer, it depends on your circumstances.

For my own purposes, I'm using your type 1. I don't have access to the TR1 library. I do have complete control over all the classes I need to have shared pointers to. The additional memory and time efficiency of type 1 might be pretty slight, but memory usage and speed are big issues for my code, so type 1 was a slam dunk.

On the other hand, for anyone who can use TR1, I'd think the type 2 std::tr1::shared_ptr class would be a sensible default choice, to be used whenever there isn't some pressing reason not to use it.

Simplism answered 2/2, 2009 at 17:13 Comment(0)
D
1

The problem with 2 can be worked around. Boost offers boost::shared_from_this for this same reason. In practice, it's not a big problem.

But the reason they went with your option #2 is that it can be used in all cases. Relying on inheritance isn't always an option, and then you're left with a smart pointer you can't use for half your code.

I'd have to say #2 is best, simply because it can be used in any circumstances.

Dost answered 2/2, 2009 at 16:53 Comment(0)
R
1

Our project uses smart pointers extensively. In the beginning there was uncertainty about which pointer to use, and so one of the main authors chose an intrusive pointer in his module and the other a non-intrusive version.

In general, the differences between the two pointer types were not significant. The only exception being that early versions of our non-intrusive pointer implicitly converted from a raw pointer and this can easily lead to memory problems if the pointers are used incorrectly:

void doSomething (NIPtr<int> const &);

void foo () {
  NIPtr<int> i = new int;
  int & j = *i;
  doSomething (&j);          // Ooops - owned by two pointers! :(
}

A while ago, some refactoring resulted in some parts of the code being merged, and so a choice had to be made about which pointer type to use. The non-intrusive pointer now had the converting constructor declared as explicit and so it was decided to go with the intrusive pointer to save on the amount of code change that was required.

To our great surprise one thing we did notice was that we had an immediate performance improvement by using the intrusive pointer. We did not put much research into this, and just assumed that the difference was the cost of maintaining the count object. It is possible that other implementations of non-intrusive shared pointer have solved this problem by now.

Ramunni answered 2/2, 2009 at 18:16 Comment(1)
This is why I've started typedefing a smart pointer in each class that will be used with a smart pointer. Then the decision can be made on a per-class basis, and changed without affecting client code. This only works as long as both pointer types have the same interface.Heelandtoe
B
1

What you are talking about are intrusive and non-intrusive smart pointers. Boost has both. boost::intrusive_ptr calls a function to decrease and increase the reference count of your object, everytime it needs to change the reference count. It's not calling member functions, but free functions. So it allows managing objects without the need to change the definition of their types. And as you say, boost::shared_ptr is non-intrusive, your category 2.

I have an answer explaining intrusive_ptr: Making shared_ptr not use delete. In short, you use it if you have an object that has already reference counting, or need (as you explain) an object that is already referenced to be owned by an intrusive_ptr.

Belong answered 2/2, 2009 at 19:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.