Example to Use Shared_Ptr

Example to use shared_ptr?

Using a vector of shared_ptr removes the possibility of leaking memory because you forgot to walk the vector and call delete on each element. Let's walk through a slightly modified version of the example line-by-line.

typedef boost::shared_ptr<gate> gate_ptr;

Create an alias for the shared pointer type. This avoids the ugliness in the C++ language that results from typing std::vector<boost::shared_ptr<gate> > and forgetting the space between the closing greater-than signs.

    std::vector<gate_ptr> vec;

Creates an empty vector of boost::shared_ptr<gate> objects.

    gate_ptr ptr(new ANDgate);

Allocate a new ANDgate instance and store it into a shared_ptr. The reason for doing this separately is to prevent a problem that can occur if an operation throws. This isn't possible in this example. The Boost shared_ptr "Best Practices" explain why it is a best practice to allocate into a free-standing object instead of a temporary.

    vec.push_back(ptr);

This creates a new shared pointer in the vector and copies ptr into it. The reference counting in the guts of shared_ptr ensures that the allocated object inside of ptr is safely transferred into the vector.

What is not explained is that the destructor for shared_ptr<gate> ensures that the allocated memory is deleted. This is where the memory leak is avoided. The destructor for std::vector<T> ensures that the destructor for T is called for every element stored in the vector. However, the destructor for a pointer (e.g., gate*) does not delete the memory that you had allocated. That is what you are trying to avoid by using shared_ptr or ptr_vector.

shared_ptr Real life use-cases

In our simulator product, we use a framework to deliver messages between simulation components (called endpoints). These endpoints could reside on multiple threads within a process, or even on multiple machines in a simulation cluster with messages routed through a mesh of RDMA or TCP connections. The API looks roughly like:

class Endpoint {
public:
// Fill in sender address, etc., in msg, then send it to all
// subscribers on topic.
void send(std::unique_ptr<Message> msg, TopicId topic);

// Register this endpoint as a subscriber to topic, with handler
// called on receiving messages on that topic.
void subscribe(TopicId topic,
std::function<void(std::shared_ptr<const Message>)> handler);
};

In general, once the sender endpoint has executed send, it does not need to wait for any response from any receiver. So, if we were to try to keep a single owner throughout the message routing process, it would not make sense to keep ownership in the caller of send or otherwise, send would have to wait until all receivers are done processing the message, which would introduce an unnecessary round trip delay. On the other hand, if multiple receivers are subscribed to the topic, it also wouldn't make sense to assign unique ownership to any single one of them, as we don't know which one of them needs the message the longest. That would leave the routing infrastructure itself as the unique owner; but again, in that case, then the routing infrastructure would have to wait for all receivers to be done, while the infrastructure could have numerous messages to deliver to multiple threads, and it also wants to be able to pass off the message to receivers and be able to go to the next message to be delivered. Another alternative would be to keep a set of unique pointers to messages sent waiting for threads to process them, and have the receivers notify the message router when they're done; but that would also introduce unnecessary overhead.

On the other hand, by using shared_ptr here, once the routing infrastructure is done delivering messages to incoming queues of the endpoints, it can then release ownership to be shared between the various receivers. Then, the thread-safe reference counting ensures that the Message gets freed once all the receivers are done processing it. And in the case that there are subscribers on remote machines, the serialization and transmission component could be another shared owner of the message while it's doing its work; then, on the receiving machine(s), the receiving and deserialization component can pass off ownership of the Message copy it creates to shared ownership of the receivers on that machine.

How to properly use shared_ptr in good C++ APIs

Using shared pointers for the purpose you describe is reasonable and increasingly common in C++11 libraries.

A few points to note:

  • On an API, taking a shared_ptr as an argument forces the caller construct a shared_ptr. This is definitely a good move where there is a transfer of ownership of the pointee. In cases where the function merely uses a shared_ptr, it may be acceptable to take a reference to the object or the shared_ptr

  • You are using shared_ptr<Parent> to hold a back reference to the parent object whilst using one in the other direction. This will create a retain-cycle resulting in objects that never get deleted. In general, used a shared_ptr when referencing from the top down, and a weak_ptr when referencing up. Watch out in particular for delegate/callback/observer objects - these almost always want a weak_ptr to the callee. You also need to take care around lambdas if they are executing asynchronously. A common pattern is to capture a weak_ptr.

  • Passing shared pointers by reference rather than value is a stylistic point with arguments for and against. Clearly when passing by reference you are not passing ownership (e.g. increasing the reference count on the object). On the other hand, you are also not taking the overhead either. There is a danger that you under reference objects this way. On a more practical level, with a C++11 compiler and standard library, passing by value should result in a move rather than copy construction and be very nearly free anyway. However, passing by reference makes debugging considerably easier as you won't be repeatedly stepping into shared_ptr's constructor.

  • Construct your shared_ptr with std::make_shared rather than new() and shared_ptr's constructor

    shared_ptr<Parent> parent = std::make_shared<Parent>();

    With modern compilers and libraries this can save a call to new().

  • both shared_ptr and weak_ptr can contain NULL - just as any other pointer can. You should always get in the habit of checking before dereferencing and probably assert()ing liberally too. For the constructor case, you can always accept NULL pointers and instead throw at the point of use.

  • You might consider using a typedef for your shared pointer type. One style that is sometimes used is follows:

    typedef std::weak_ptr<Parent> Parent_P;

    typedef std::shared_ptr<Parent> Parent_WkP;

    typedef std::weak_ptr<Child> Child_P;

    typedef std::shared_ptr<Child> Child_WkP;

  • It's also useful to know that in header files you can forward declare shared_ptr<Type> without having seen a full declaration for Type. This can save a lot of header bloat

New to c++11 features, proper use of shared_ptr?

...it's useful when many instances may be sharing the same object. Correct?

Not exactly. It is useful when many instances may be owning the same object. Sharing is not enough to justify using a std::shared_ptr as there is certainly some overhead to using it.

When you create dynamic resources you need to think about ownership ie. who is responsible for deleting it? The object that should be responsible for deleting the resource should manage the resource using some kind of smart pointer (or a container).

If only one object is responsible for deciding when the resource must be deleted then use a std::unique_ptr. If other objects/functions need to share access to the resource but will never be responsible for deleting it, then pass them a reference or a raw pointer to the resource.

The time to use a std::shared_ptr is when you can not know which of the objects that are sharing the resource will be the one that needs to delete it. In that case each object should hold ownership of the resource by holding a std::shared_ptr.

And even when several objects share ownership through a std::shared_ptr they should still only pass a reference or a raw pointer to objects/functions that do not need ownership rights.

Another problem with passing std::stared_ptr round willy-nilly (where they are not needed) is that they can suffer from the Java memory leak problem. That is when objects never die because some reference to them remains in a forgotten part of the software. They can gradually accumulate and eat away your memory.

Typically you should prefer keeping your resources in a container like a std::vector or a std::map:

std::map<int, Tile> Tiledb;

The container manages the destruction of the Tile so no need for a smart pointer.

If, however, you were using polymorphic Tile objects then you would need to store them using a pointer. For this prefer to use std::unique_ptr:

// When the Tiles are no longer needed after the map is destroyed
std::map<int, std::unique_ptr<Tile>> Tiledb;

If other objects need to keep accessing the Tile objects after the map is destroyed then a std::shared_ptr may be appropriate:

// only when Tiles need to keep living after the map is destroyed.
std::map<int, std::shared_ptr<Tile>> Tiledb;

When to use shared_ptr and when to use raw pointers?

Your analysis is quite correct, I think. In this situation, I also would return a bare B*, or even a [const] B& if the object is guaranteed to never be null.

Having had some time to peruse smart pointers, I arrived at some guidelines which tell me what to do in many cases:

  • If you return an object whose lifetime is to be managed by the caller, return std::unique_ptr. The caller can assign it to a std::shared_ptr if it wants.
  • Returning std::shared_ptr is actually quite rare, and when it makes sense, it is generally obvious: you indicate to the caller that it will prolong the lifetime of the pointed-to object beyond the lifetime of the object which was originally maintaining the resource. Returning shared pointers from factories is no exception: you must do this eg. when you use std::enable_shared_from_this.
  • You very rarely need std::weak_ptr, except when you want to make sense of the lock method. This has some uses, but they are rare. In your example, if the lifetime of the A object was not deterministic from the caller's point of view, this would have been something to consider.
  • If you return a reference to an existing object whose lifetime the caller cannot control, then return a bare pointer or a reference. By doing so, you tell the caller that an object exists and that she doesn't have to take care of its lifetime. You should return a reference if you don't make use of the nullptr value.

Working with shared_ptr returned from another concrete class

The difference between shared_ptr and weak_ptr is that weak_ptr does not increase the ref count on the object and does prevent the object from being deleted.

This has its pros and cons, if you perform async operations after you obtain the pointer and are unsure whether the object that provided the data has been destroyed then you can use weak_ptr and check if you still have access to the object.

If not then keep it simple and use shared_ptr.

How can I use shared_ptr using PostThreadMessage?

Since the message parameters have to outlive the calling scope, it does not make much sense to use shared_ptr in this example, as the source shared_ptr is most likely to go out of scope before the message is processed. I would suggest using unique_ptr instead, so that you can release() the pointer while it is in flight, and then obtain new ownership of it once the message is processed:

SendingMethod::SendMsgId( ... )
{
...

std::unique_ptr<MyParams> myParams( new MyParams(value1, value2, value3) );
if (PostThreadMessage(MSG_ID, 0, reinterpret_cast<LPARAM>(myParams.get()))
myParams.release();

...
}

ReceivingMethod::OnMsgId( WPARAM wParam, LPARAM lParam)
{
std::unique_ptr<MyParams> myParams( reinterpret_cast<MyParams*>(lParam) );
... // use object
}

Should I use shared_ptr or unique_ptr?

Short answer: depends.

It depends on if the pointer returned by getB may be stored/used somewhere while the owning A has gone out of scope. The difference is about ownership not about how many pointers you do have.

  • If the A still exists when you use the result of getB , you can store a unique_ptr and return a plain pointer (or a reference if getB can never return nullptr). That expresses "A owns B, and no-one else does".
  • If the A might go out of scope while you are using/holding the result of getB, but the B should go out of scope together with (or shortly after) the A, store a shared_ptr and return a weak_ptr.
  • If possibly many objects, including the callers of getB, may hold on to the B and there is no clear single owner, store and return shared_ptrs.


Related Topics



Leave a reply



Submit