1

I am busy designing a new C++ application. In this application I want to minimize potential errors with pointers, and since the application should be plain C++ (no .Net or other fancy things), I am investigating shared pointers and I am considering using shared pointers everywhere (instead of normal pointers).

I worked out some tricks to make it easier to work with shared pointers, e.g.: using a typedef within the class, like this:

class X
   {
   public:
      ...
      typedef std::shared_ptr<X> Ptr;
   };

That way you can easily write X::Ptr, which is easier to write than "std::shared_ptr" everywhere.

I also noticed some disadvantages to shared pointers:

  • Everywhere I use a shared pointer I need to include <memory>
  • I can't use forward declaration anymore if I just want to use the pointer

Are there any other tricks to make shared pointers easier to work with?

MSalters
  • 173,980
  • 10
  • 155
  • 350
Patrick
  • 23,217
  • 12
  • 67
  • 130
  • 2
    I used a typedef like you suggested before, but stopped doing that because I find using `shared_ptr` directly to be more readable. Instead, I prefer to pull shared_ptr into global namespace to make it easier to write (via `using std::shared_ptr;`). Matter of personal taste tho. – ltjax Dec 21 '10 at 08:53

5 Answers5

8

DON'T!

The only way to minimize pointer errors is to use the right pointer types. And that's types, plural.

Shared pointers are not a silver bullet. They become memory leaks as soon when you have cyclical references (and if plan to use them everywhere, those will show up pretty quickly)

If you want error-free C++ applications, you have to work for it. You have to understand your application. You have to understand the ownership semantics of different objects. Shared pointers just give you shared ownership, which is generally a decent lowest denominator. Everything else can be replaced by shared ownership and it'll work, sort of.

But the default case is that an object is owned by one other entity. It is owned by a function, and should be destroyed when that function returns, or it is owned by a class, or whatever else. Often, you don't need pointers at all. the object is stored by value in a std::vector, perhaps. Or it is just a local variable or a class member. If it is a pointer, it'll often be better expressed by a scoped_ptr or perhaps one which allows transfer of ownership (unique_ptr or auto_ptr).

shared_ptr is what you might fall back to when you can give no guarantees about the lifetime or ownership of an object. But when you use that, you also need to use weak_ptr to break cycles.

Really, a better approach is to avoid pointers as much as at all possible. When you do need a pointer, use one which has the most specific ownership semantics possible (prefer scoped_ptr, which doesn't allow transfer of ownership at all, then if you need it, fall back to one which allows you to move ownership, such as unique_ptr, and only as a last resort should you use shared_ptr, which allows you to share ownership freely among any number of clients.

There's no magic wand you can wave to make your C++ code "just work". The only way to achieve that is to write good solid C++ code. And you do that by knowing, and using, the tools at your disposal, not by pretending that "hey, shared_ptr is just like a garbage collector, isn't it? I can just ignore all questions of object lifetime or memory leaks if I use it".

jalf
  • 243,077
  • 51
  • 345
  • 550
  • Shouting DON't is a bit over-dramatic :-) `Be Careful thats not quite what you want to do` might be slightly kinder to people's virtual ears. Otherwise I totally agree. – Martin York Dec 21 '10 at 10:43
  • Actually, because of how it works, shared_ptr can serve in cases when you are passing a pointer to a local object. You can set the deleter to a null operation and then send it down the line. You are of course breaking every semantic about shared_ptr, but in a pinch it does the trick. – Edward Strange Dec 21 '10 at 17:17
  • By definition there's _something_ wrong with being **over** dramatic, if it wasn't _over_ it would be _dramatic_ once it's overdramatic by definition there's something wrong with it – Motti Dec 21 '10 at 18:56
  • @Motti: Bah, that's only only if you believe in logic! ;) – jalf Dec 21 '10 at 20:05
  • @Motti: ergo it's not over dramatic. Screaming DON'T in this case is quite warranted. – Edward Strange Dec 23 '10 at 21:29
3

Don't just pick one smart pointer to use everywhere. Not every screw is a Phillips head.

Also, you can use forward declarations for any smart pointer exactly the same as raw pointers:

struct X;

...
std::shared_ptr<X> ptr;  // legal

struct X {};
ptr = std::shared_ptr<X>(new X); // legal

This is the second time I've heard this misconception on SO and it's simply 100% false.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
1

Regarding the typedef, you should take a look on this question which addresses the exact same issue.

For the second part, I believe you are mistaken as std::shared_ptr can be used for incomplete types, as specified in 20.9.10.2/2 of N3225 :

The template parameter T of shared_ptr may be an incomplete type.

If your current implementation does not support that, I think it should be considered a bug.

Community
  • 1
  • 1
icecrime
  • 74,451
  • 13
  • 99
  • 111
  • 1
    I can be incomplete on declaration, but not on usage. shared_ptr depends on T's destructor, unlike T* – BatchyX Dec 21 '10 at 08:49
  • @BatchyX: So at the point the destructor is used (just like when you use delete) the definition has to be available. Not such a big change. – Martin York Dec 21 '10 at 10:40
  • Not every usage of shared_ptr results in a destructor call. For example, a function that merely returns a pointer retrieved from a container doesn't have to destruct anything, and it can be done with an incomplete type. replacing this pointer by a shared_ptr forces the type to be defined, even if the delete cannot be called in this function. – BatchyX Dec 21 '10 at 11:09
1

It is quite common in code to have a "convention" which you then follow, and as long as it is done consistently across your team etc. people will be able to understand it.

It is far better to declare your shared pointer typedefs in a "forward" file. Something like this:

namespace boost { template< typename T > class shared_ptr; }

namespace MyStuff
{
   class Foo;
   class Bar;
   class Baz;

   typedef boost::shared_ptr<Foo> FooPtr;
   typedef boost::shared_ptr<Bar> BarPtr;
   typedef boost::shared_ptr<Baz> BazPtr;

}

You will sometimes want to use other pointers than shared_ptr but your convention would then be to use a different notation with XPtr meaning shared_ptr.

Of course you might use a different notation.

I think we use FooWeakPtr to mean weak_ptr<Foo> FooConstPtr to mean shared_ptr<const Foo>

for example.

Should be in your coding standards document.

CashCow
  • 30,981
  • 5
  • 61
  • 92
0

I prefer this kind of approach to it:

   namespace Internal
   {
      template <class T>
      struct DeclareShared
      {
         typedef std::shared_ptr<T> type;
      };

      template <class T>
      struct DeclareUnique
      {
         typedef std::unique_ptr<T> type;
      };
   }

   // Inherit one of these classes to use a generic smart pointer interface.

   // If class is abstract, use this interface.
   template <class T>
   struct SharedAbstract
   {
      typedef typename Internal::DeclareShared<T>::type APtr;
   };

   template <class T>
   struct Shared
   {
      typedef typename Internal::DeclareShared<T>::type Ptr;

      template <class... P>
      static Ptr shared(P&&... args) 
      { 
         return std::make_shared<T>(std::forward<P>(args)...); 
      }
   };

   template <class T>
   struct UniqueAbstract
   {
      typedef typename Internal::DeclareUnique<T>::type AUPtr;
   };

   template <class T>
   struct Unique
   {
      typedef typename Internal::DeclareUnique<T>::type UPtr;

      template <class... P>
      static UPtr shared(P&&... args) 
      { 
         return std::unique_ptr<T>(new T(std::forward<P>(args)...)); 
      }
   };

Added more interfaces for scoped ptr, etc and whatever you'd want you could do things like:

struct Foo : public Shared<Foo>, public Unique<Foo>
{};

Foo::Ptr foo = Foo::shared();
Foo::UPtr unique = Foo::unique();

I'm not sure how VS10 copes with variadic templates, but this works fine on GCC4.5+ at least.

Maister
  • 4,978
  • 1
  • 31
  • 34