Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shared_ptr Real life use-cases

shared_ptr is to be used when we have a scenario where it is desirable to have multiple owners of a dynamically allocated item.

Problem is, I can't imagine any scenario where we require multiple owners. Every use-case I can image can be solved with a unique_ptr.

Could someone provide a real life use-case example with code where shared_ptr is required (and by required, I mean the optimal choice as a smart pointer)? And by "real life" I mean some practical and pragmatic use-case, not something overly abstract and fictitious.

like image 247
code Avatar asked Feb 16 '18 20:02

code


People also ask

When should you use shared_ptr?

So, we should use shared_ptr when we want to assign one raw pointer to multiple owners. // referring to the same managed object. When to use shared_ptr? Use shared_ptr if you want to share ownership of a resource.

In what situation is a shared_ptr more appropriate than a unique_ptr?

Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.

Why would you choose shared_ptr instead of unique_ptr?

Use unique_ptr when if you want to have single ownership(Exclusive) of resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. Use shared_ptr if you want to share ownership of resource .

What is unique_ptr used for?

std::unique_ptr. std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.


4 Answers

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.

like image 150
Daniel Schepler Avatar answered Oct 20 '22 10:10

Daniel Schepler


In a CAD app, I use shared_ptr to save RAM and VRAM when multiple models happen to have a same mesh (e.g. after user copy-pasted these models). As a bonus, multiple threads can access meshes at the same time, because both shared_ptr and weak_ptr are thread safe when used correctly.

Below’s a trivial example. The real code is way more complex due to numerous reasons (GPU buffers, mouse picking, background processing triggered by some user input, and many others) but I hope that’s enough to give you an idea where shared_ptr is justified.

// Can be hundreds of megabytes in these vectors
class Mesh
{
    std::string name;
    std::vector<Vector3> vertices;
    std::vector<std::array<uint32_t, 3>> indices;
    BoundingBox bbox;
};

// Just 72 or 80 bytes, very cheap to copy.
// Can e.g. pass copies to another thread for background processing.
// A scene owns a collection of these things.
class Model
{
    std::shared_ptr<Mesh> mesh;
    Matrix transform;
};
like image 25
Soonts Avatar answered Oct 20 '22 10:10

Soonts


In my program's user interface, I have the concept of "control point values" (a control point value represents the current state of a control on the hardware my program controls), and (of course) the concept of "widgets" (a widget is a GUI component that renders the current state of a control point to the monitor, for the user to see and/or manipulate).

Since it is a pretty elaborate system that it needs to control, we have

  • lots of different types of control point values (floats, ints, strings, booleans, binary blobs, etc)
  • lots of different types of widget (text displays, faders, meters, knobs, buttons, etc)
  • lots of different ways that a given widget could choose to render a particular control point value as text (upper case, lower case, more or fewer digits of precision, etc)

If we just did the obvious thing and wrote a new subclass every time we needed a new combination of the above, we'd end up with a geometric explosion of thousands of subclasses, and therefore a very large codebase that would be difficult to understand or maintain.

To avoid that, I separate out the knowledge of "how to translate a control point value into human-readable text in some particular way" into its own separate immutable object that can be used by anyone to do that translation, e.g.

// My abstract interface
class IControlPointToTextObject
{
public:
   virtual std::string getTextForControlPoint(const ControlPoint & cp) const = 0;
};

// An example implementation
class RenderFloatingPointValueAsPercentage : public IControlPointToTextObject
{
public:
   RenderFloatingPointValueAsPercentage(int precision) : m_precision(precision)
   {
      // empty
   }

   virtual std::string getTextForControlPoint(const ControlPoint & cp) const = 0
   {
      // code to create and return a percentage with (m_precision) digits after the decimal point goes here....
   }

private:
   const int m_precision;
};

... so far, so good; now e.g. when I want a text widget to display a control point value as a percentage with 3 digits of after the decimal point, I can do it like this:

TextWidget * myTextWidget = new TextWidget;
myTextWidget->setTextRenderer(std::unique_ptr<IControlPointToTextObject>(new RenderFloatingPointValueAsPercentage(3)));

... and I get what I want. But my GUIs can get rather elaborate, and they might have a large number (thousands) of widgets, and with the above approach I would have to create a separate RenderFloatingPointValueAsPercentage object for each widget, even though most of the RenderFloatingPointValueAsPercentage objects will end up being identical to each other. That's kind of wasteful, so I change my widget classes to accept a std::shared_ptr instead, and now I can do this:

std::shared_ptr<IControlPointToTextObject> threeDigitRenderer = std::make_shared<RenderFloatingPointValueAsPercentage>(3);

myWidget1->setTextRenderer(threeDigitRenderer);
myWidget2->setTextRenderer(threeDigitRenderer);
myWidget3->setTextRenderer(threeDigitRenderer);
[...]

No worries about object lifetimes, no dangling pointers, no memory leaks, no unnecessary creation of duplicate renderer objects. C'est bon :)

like image 2
Jeremy Friesner Avatar answered Oct 20 '22 12:10

Jeremy Friesner


Take any lambda, called within a member function, f, of a class, C, where you want to deal with an object that you would pass into the lambda [&] as a reference. While you are waiting inside f for the lambda to finish, C goes out of scope. The function is gone and you have a dangling reference. Segmentation fault is as close as you get to defined behavior, when the lambda is next accessing the reference. You cannot pass the unique punter into the lambda. You couldn't access it from f once it's moved. The solution: shared pointer and [=]. I code the core of a database. We need shared pointers all the time in a multi-threaded infrastructure. Don't forget about the atomic reference counter. But your general scepticism is appreciated. Shared punters are used nearly always when one doesn't need them.

like image 1
Kaveh Vahedipour Avatar answered Oct 20 '22 10:10

Kaveh Vahedipour