Jump to content
  • Advertisement
Sign in to follow this  
azherdev

To boost::shared_ptr or not to boost::shared_ptr

This topic is 3707 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Was wondering whether or not to base my game on shared_ptr to help reduce memory leaks and other bugs. I'm not creating Doom 4 but nor am I making a minesweeper game. Any reason to not use shared_ptr throughout the engine and game logic? I have revisited the idea since TR1 (with shared_ptr) is included in VS2008 SP1 beta. I will use boost implementation until SP1 is released.

Share this post


Link to post
Share on other sites
Advertisement
If you need shared ownership semantics, then by all means use it. It works very well for that (provided you are aware of circular referencing problems).

When working in C++, you can usually find a superior alternative to raw pointers most of the time. That alternative isn't necessarily shared_ptr, but its a common and useful tool.

I'm not sure what you mean by "base my game on shared_ptr". shared_ptr is a tool, an implementation detail. Use it only where appropriate.

Share this post


Link to post
Share on other sites
I've been reading up on TR1 for C++ and some have advocated to use the shared_ptr instead of raw pointer from now on unless speed is critical.

Microsoft programmer who is on the VC++ dev team, suggests we should use shared_ptr from now on. He likens it to using std::vector<MyObject> to creating an array manually MyObject[200]. Of course, edge cases could use manual optimization.

I've done "sizeof" on the smart pointer and it returns 8. Am I to believe that the smart pointer is therefore 8 bytes total? Which isn't a big deal going from a 4 byte pointer (32bit OS).

Would it be foolish to use shared_ptr from now on rather than raw pointers?

P.S. I'm not creating/destroying objects in tight loops within loops.

[EDIT] PSS. Of course I'm also talking about using them on object needed to reside on heap, stack objects will not use shared_ptr.

Share this post


Link to post
Share on other sites
Quote:
Original post by azherdev
I've done "sizeof" on the smart pointer and it returns 8. Am I to believe that the smart pointer is therefore 8 bytes total?


shared_ptr is pretty much guaranteed to be the size of two pointers. The first pointer points to your heap allocated object, and the second pointer points to a heap allocated reference count.

intrusive_ptr OTOH, is pretty much guaranteed to be exactly the size of one pointer.

I say "pretty much" in both cases because it is conceivable that padding could cause the objects to be larger, but that isn't the case on the compilers/cpus you're to likely encounter.

Quote:
Original post by azherdev
Which isn't a big deal going from a 4 byte pointer (32bit OS).


You're right. It isn't a big deal at all.

Quote:
Original post by azherdev
Would it be foolish to use shared_ptr from now on rather than raw pointers?


Off the top of my head I can think of one reason not to. For example shared_ptr's reference count is not thread safe. Though you can get around this by implementing a thread safe reference counted object that is compatible with intrusive_ptr.

Quote:
Original post by azherdev
PSS. Of course I'm also talking about using them on object needed to reside on heap, stack objects will not use shared_ptr.


You don't have to restrict your use of shared_ptr to managing objects on the heap. Consider this somewhat contrived example:


// Custom deleter function object
struct CustomDeleter
{
operator()(FILE* fp)
{
fclose(fp);
}
};

...

// Create a shared_ptr representing a resource
shared_ptr<FILE> myFileHandle( fopen("MyFile.txt", "r"), CustomDeleter());


In the above code we create a shared_ptr to automatically close our file handle when it is no longer needed. This is achieved through the use of a custom deleter function object.

Share this post


Link to post
Share on other sites
Reading the following link:

http://www.boost.org/doc/libs/1_35_0/libs/smart_ptr/shared_ptr.htm#ThreadSafety

I'm a bit confused. The undefined behavior that they talk about has to do with the object that the shared_ptr points to or the shared_ptr object itself? They mention something about atomic reference count.

I understand that if thread A reduces the counter to 0 and thread B tries to access the object while it is being destroyed by thread A. Is that the only problem or can the reference count in the shared_ptr actually screw up and either create a resource leak or prematurely reduce the reference count to 0?

Share this post


Link to post
Share on other sites
Quote:
Original post by azherdev
I'm a bit confused. The undefined behavior that they talk about has to do with the object that the shared_ptr points to or the shared_ptr object itself?

The shared_ptr object itself. shared_ptr has no way of making thread safety guarantees beyond itself.

Quote:
I understand that if thread A reduces the counter to 0 and thread B tries to access the object while it is being destroyed by thread A. Is that the only problem

No.

Quote:
or can the reference count in the shared_ptr actually screw up and either create a resource leak or prematurely reduce the reference count to 0?

Yes. If thread A reassigns shared_ptr P while thread B tries to copy P, chances are high that eventually you'll end up with a copy which points at one item but refers to the reference count of another item.

If you need to share a single shared_ptr instance between threads that's not immutable, you'd want to use something like boost::shared_mutex -- lock_shared()ing before reading and lock()ing (and exclusive lock) before writing.

Share this post


Link to post
Share on other sites
Quote:

I understand that if thread A reduces the counter to 0 and thread B tries to access the object while it is being destroyed by thread A. Is that the only problem or can the reference count in the shared_ptr actually screw up and either create a resource leak or prematurely reduce the reference count to 0?

This shouldn't really ever happen. Generally speaking there is no race condition for the reference counter as the increase and decrease operation is implemented in an atomic fashion. On Windows there's the InterlockedIncrement and InterlockedDecrement functions. I would be surprised if the boost shared_ptr doesn't use the interlocked variable access functions if you look at the detail files.

The reset function however probably isn't protected as it operates on the object pointer and on the reference counter pointer. This means that you would have to protect it with a critical section or similar heavier synchronization object which is probably outside of the lightness requirement of the shared_ptr. I haven't looked at it though so it's just speculation.

Share this post


Link to post
Share on other sites
I use the Boost.Pointers library exclusively in my projects and it has been very, very helpful. (However, neither is multithreaded, so I never had to deal with any of those tough issues.)

I actually use a scheme where every class I want to have "managed" has a set of pointers generated for it in a file I usually name pointers.hpp. My game engine, Mocha, defines macros for this that child projects also use in their own pointers.hpp file.

This is straight from my repository.


#ifndef MOCHA_POINTERS_HEADER
#define MOCHA_POINTERS_HEADER

/**
* @file Contains forward declarartions and pointer type declarations for commonly used objects, particularly reference counted objects. Types are created of the form *_ptr_t and const_*_ptr_t. Additionally, helper function to create new pointers are created of the form new_*().
*/


#include <boost/shared_ptr.hpp>

#define MOCHA_PTR_DECL(X) class X ; typedef X * X##_ptr_t; typedef const X * const_##X##_ptr_t;
#define MOCHA_NAMED_PTR_DECL(X,Y) class X ; typedef X * Y##_ptr_t; typedef const X * const_##Y##_ptr_t;
#define MOCHA_SHARED_PTR_DECL(X) class X ; typedef boost::shared_ptr< X > X##_ptr_t; typedef boost::shared_ptr<const X > const_##X##_ptr_t; typedef boost::weak_ptr< X > X##_wptr_t; typedef boost::weak_ptr<const X > const_##X##_wptr_t;
#define MOCHA_NAMED_SHARED_PTR_DECL(X,Y) class X ; typedef boost::shared_ptr< X > Y##_ptr_t; typedef boost::shared_ptr<const X > const_##Y##_ptr_t; typedef boost::weak_ptr< X > Y##_wptr_t; typedef boost::weak_ptr<const X > const_##Y##_wptr_t;
#define MOCHA_PTR_NEW_DECL(X) X##_ptr_t new_##X () { return new X (); }
#define MOCHA_SHARED_PTR_NEW_DECL(X) inline X##_ptr_t new_##X () { return X##_ptr_t(new X ()); }

namespace mocha {
MOCHA_SHARED_PTR_DECL(kernel)
MOCHA_SHARED_PTR_DECL(module)
namespace events {
MOCHA_SHARED_PTR_DECL(event_queue)
MOCHA_SHARED_PTR_DECL(event_queue_impl)
}
namespace graphics {
MOCHA_SHARED_PTR_DECL(basic_vertex_buffer)
MOCHA_NAMED_SHARED_PTR_DECL(basic_vertex_buffer, vertex_buffer)
MOCHA_SHARED_PTR_DECL(bitmap_font)
MOCHA_SHARED_PTR_DECL(bitmap_text)
MOCHA_SHARED_PTR_DECL(graphics)
MOCHA_SHARED_PTR_DECL(graphics_impl)
MOCHA_SHARED_PTR_DECL(image)
MOCHA_SHARED_PTR_DECL(job_buffer)
MOCHA_SHARED_PTR_DECL(paintable)
MOCHA_SHARED_PTR_DECL(paint_job)
MOCHA_SHARED_PTR_DECL(raster)
MOCHA_SHARED_PTR_DECL(raster_sprite)
MOCHA_SHARED_PTR_DECL(scene_node)
MOCHA_SHARED_PTR_DECL(sequence)
MOCHA_SHARED_PTR_DECL(texture)
MOCHA_SHARED_PTR_DECL(textured_vertex_buffer)
MOCHA_SHARED_PTR_DECL(vertex_buffer_base)
}
namespace input {
MOCHA_SHARED_PTR_DECL(input)
MOCHA_SHARED_PTR_DECL(input_impl)
MOCHA_SHARED_PTR_DECL(joystick)
}
namespace network {
MOCHA_SHARED_PTR_DECL(network)
MOCHA_SHARED_PTR_DECL(network_impl)
MOCHA_SHARED_PTR_DECL(socket)
}
namespace timing {
MOCHA_SHARED_PTR_DECL(timer)
MOCHA_SHARED_PTR_DECL(timer_impl)
}
}

#endif






The macros that include SHARED in their name create a series of typedefs based on boost::shared_ptr and boost::weak_ptr (both are needed, because circular dependencies need to be kept in mind). The naming convention is very convenient, and other projects of mine take advantage of the other macros that use plain old pointers for consistent naming.

For a class foo, the typedefs created are foo_ptr_t, foo_wptr_t, const_foo_ptr_t, and const_foo_wptr_t. I'm considering having the weak pointer typedefs renamed to yield foo_weak_ptr_t, since the 'w' in the current convention is very easy to miss.

For other "non-managed" types, I use a different macro, but get the same naming convention. For a "manual" class bar, the typedefs bar_ptr_t and const_bar_ptr_t are generated (note that there are no weak pointer types in this case).

Anyway, this has worked very well for me thus far and is a very important part of resource management in my system as many resources are (nearly) "self managing". In the end, client code is much simpler and less error prone.

In short, I'd go for it. Just my two cents. :-) I dunno if this was helpful, but I thought I'd share.

Share this post


Link to post
Share on other sites
IMO shared_ptr is pure evil and should not be used.
It is only to manage shared ownership, which shouldn't even happen in a normal program, unless as a low-level implicit sharing optimization.
A good C++ program shouldn't use pointers at all, but only values and weak references.

Share this post


Link to post
Share on other sites
Quote:

IMO shared_ptr is pure evil and should not be used.
It is only to manage shared ownership, which shouldn't even happen in a normal program, unless as a low-level implicit sharing optimization.
A good C++ program shouldn't use pointers at all, but only values and weak references.

This is wrong. The semantics of the C++ reference qualifier do not allow some types of important referential semantics (null, reseatable) that are required to implement many useful constructs in a sane fashion (such as linked lists). Values are not suitable any place referential semantics are required.

C++'s pointer mechanism isn't necessarily the best way to acquire said referential semantics, but abstractions on top if -- like shared_ptr and (in appropriate cases) auto_ptr and the like are excellent.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!