开发者

Ways to make it easier to work with shared pointers in C++

开发者 https://www.devze.com 2023-02-01 02:09 出处:网络
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 fanc

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?


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".


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.


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.


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消