28

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.

34
  • 6
    I already searched, long and hard. While there are countless examples of how to use shared_ptr, I have yet to see practical use-case examples for it. Most of the time, it is used when it shouldn't be (this includes most examples on web).
    – code
    Commented Feb 16, 2018 at 20:23
  • 5
    "Every use-case of having multiple owners of a dynamically allocated item I can image can be solved with a unique_ptr." I'm curious as to how you are managing to imagine having multiple owners of a unique_ptr.
    – Eljay
    Commented Feb 16, 2018 at 20:28
  • 2
    Eljay, also, to be fair, you have in quotes (quoting me) saying something I didn't say. I said, "Every use-case I can image can be solved with a unique_ptr." That is not the same as what you quoted. No big deal, I hope my question and motives are a bit more clear.
    – code
    Commented Feb 16, 2018 at 20:35
  • 9
    I vote for reopen because this question is rather direct and clear: give a valid example of code where use of shared_ptr would be the only acceptable solution. I personally can not came up with such a code example. Note that something like "there are gorrillion of real world programs (and even programming languages) built around shared ownership of everything" is not really an answer. Commented Feb 16, 2018 at 20:40
  • 2
    Languages like C# and Java, where all objects are by reference (and collected by the garbage collected), effectively is as if everything was analogous to C++ std::shared_ptr. For any of those use case scenarios where shared (GC'd) references are suitable, is also the situation where shared_ptr in C++ could be considered.
    – Eljay
    Commented Feb 16, 2018 at 23:27

9 Answers 9

23

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.

2
  • 1
    Hmmm, I think this is a really good example. It's the asynchronicity that is the driving force behind the need (at least in this example). Or in other words, it is what makes a single owner impractical. Great, thanks.
    – code
    Commented Feb 17, 2018 at 14:21
  • 3
    Life is given to something in one place but that place has no use for that object, its purpose is merely to give life to the object and then to pass it onto functions that independently will make actual use of it in an asynchronous way, unrelated to each other. I'll let others chime in, but I think this is a great example.
    – code
    Commented Feb 17, 2018 at 14:32
4

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;
};
16
  • 1
    Thanks Soonts, but honestly I don't get it. I don't see how you're doing the passing nor do I see multiple consumers with justified ownership. I think it is important to make the point that just because multiple items may make use of a mesh it doesn't mean they need to own it. The mesh can be owned by 1 unique_ptr and the unique_ptr can be owned by a work management class that feeds and syncs threads, and feeds them with new meshes when ready. Those threads working on the mesh data don't actually need to own it to make use of it.
    – code
    Commented Feb 17, 2018 at 4:00
  • 1
    You can pass the threads in the work management class a raw ptr to the mesh via unique_ptr s get(). With that design with unique_ptr your code would be just as flexible, and trivially faster and memory leaner. So...just as fast and memory lean. While I don't think share_ptr is wrong to use here, I don't think this illustrates an actual "need" to use it. Of course, shared_ptr can be used for any use-case. You don't need to own what you use.
    – code
    Commented Feb 17, 2018 at 4:03
  • 1
    While shared_ptr works here, if I understood your code right this is classic example of when not to use shared_ptr. Since your workers only process data (mesh), they don't need to own the mesh. They only need a raw pointer from get() method of unique_ptr. The mesh should be owned by a unique_ptr which in turn is owned higher up in the call stack by something like a mesh work management class or something tat feeds the threaded workers with a raw pointer. I don't see the need for ownership transfer or sharing.
    – code
    Commented Feb 17, 2018 at 5:21
  • 1
    I think people often end up using shared_ptr because they did not place the smart pointer that owns high enough in the call stack. They expect ref counting of shared_ptr to save them from bad code design, which it often does. Not saying that is the case here. Actually I'm not sure how you intent to pass things around since that is left out of code example.
    – code
    Commented Feb 17, 2018 at 5:28
  • 1
    Soonts, also while the work manager is involved, it is nothing that an intermediate programmer can't do in a day. Further, you have to do that work with any scheme anyway, since you need code to manage and feed the threads. It is not like that code is optional. Threads don't just read your mind and grab work and sync work on their own. Someonehas to write that code. This is not extra something. All I'm saying is that since you need that anyway, it can be the one to own the mesh and also the one to del it.
    – code
    Commented Feb 17, 2018 at 14:01
2

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 :)

2
  • Any reason why the View (as in Model/View/Controller) object couldn't be the unique owner? Commented Feb 17, 2018 at 1:19
  • Which object counts as the "view" in this scenario? What happens if there is more than one instance of that object-type present? Commented Feb 17, 2018 at 6:02
2

Suppose I want to implement a GLR parser for a language that is or contains a recursive "expression" definition. And the parsing must not just check whether the input conforms to the grammar, but also output something that can be used to do analysis, evaluations, compilations, etc. I'll need something to represent the result of each expression or subexpression grammar symbol. The actual semantic meaning of each grammar rule can be represented by polymorphism, so this will need to be some sort of pointer to a base class Expression.

The natural representation is then a std::shared_ptr<Expression>. An Expression object can be a subexpression of another compound Expression, in which case the compound Expression is the owner of the subexpression. Or an Expression object can be owned by the parse stack of the algorithm in progress, for a grammar production that has not yet been combined with other pieces. But not really both at the same time. If I were writing a LALR parser, I could probably do with std::unique_ptr<Expression>, transferring the subexpressions from the parse stack to the compound expression constructors as each grammar symbol is reduced.

The specific need for shared_ptr comes up with the GLR algorithm. At certain points, when there is more than one possible parse for the input scanned so far, the algorithm will duplicate the parse stack in order to try out tentative parses of each possibility. And as the tentative parsings proceed, each possiblity may need to use up some of those intermediate results from its own parse stack to form subexpressions of some compound expression, so now we might have the same Expression being used by both some number of parse stacks and some number of different compound Expression objects. Hopefully all but one tentative parsing will eventually fail, which means the failed parse stacks get discarded. The Expression objects directly and indirectly contained by discarded parse stacks should possibly be destroyed at that time, but some of them may be used directly or indirectly by other parse stacks.

It would be possible to do all this with just std::unique_ptr, but quite a bit more complicated. You could do a deep clone whenever parse stacks need to split, but that could be wasteful. You could have them owned by some other master container and have the parse stacks and/or compound expressions just use dumb pointers to them, but knowing when to clean them up would be difficult (and possibly end up essentially duplicating a simplified implementation of std::shared_ptr). I think std::shared_ptr is the clear winner here.

1
  • I was thinking of mentioning a similar example, where you want to implement binary search trees but with the ability to take snapshots, then make small changes. Then it would make sense to have the two trees share pointers to common (unmodified) subtrees, though the modified parts and their parents would of course need to be duplicated. Pretty much the same idea. (And very similar to what typically happens in functional programming languages like OCaml or Haskell, when you implement immutable data types and then an insert operation would return a new tree with large parts shared from input.) Commented Feb 17, 2018 at 1:33
1

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.

4
  • I'm not sure if this is actually an answer: with C++14, you can provide initializers to captures and thus use e.g. [the_unique_ptr = std::move(the_unique_ptr)] { } to pass ownership to the lambda. (That said, I have used shared_ptr in passing lambdas to Boost ASIO, as that requires a copyable handler, but it could be argued that's a shortcoming of the ASIO library which IIRC was originally written pre C++11.) Commented Feb 16, 2018 at 23:30
  • Yes, you can pass ownership down as Daniel stated. Although I don't think that is necessary. Without code I can't be totally clear what you have in mind, but due to how call stacking works, I don't actually think it is possible for a class to go out of scope whose member function is waiting for some other function down the call stack. That I'm pretty sure is guaranteed not to happen. Perhaps I'm mistaken about what code design you have in mind.
    – code
    Commented Feb 17, 2018 at 3:31
  • At level 1 you have class C. class C has f1(). At level 1, f1() is executed. f1() calls f(2). At level 2 (one level down in call stack) f(2) is working. C is at level 1 and can not go out of scope until f2() is finished and then f1() is finished. Only then can level 1 code progress. Now if f(2) is asyncronous and f1() does not wait for it, the data you are passing to f2() should not be from f1() or C. In my opinion that is a design issue. Of course, this is just my opinion. I think there are num. ways to solve this without shared ownership (so there is no "need" for it).
    – code
    Commented Feb 17, 2018 at 3:42
  • ...just to add unless the data is just going through C or F1() as a ref or ptr. and is managed by a smart pointer higher up. But that is just a given I think.
    – code
    Commented Feb 17, 2018 at 3:46
1

See this real life example. The current frame is shared across multiple consumers and with a smart pointer things get easy.

class frame { };

class consumer { public: virtual void draw(std::shared_ptr<frame>) = 0; };

class screen_consumer_t :public consumer { public:  void draw(std::shared_ptr<frame>) override {} };
class matrox_consumer_t :public consumer { public:  void draw(std::shared_ptr<frame>) override {} };
class decklink_consumer_t :public consumer { public:  void draw(std::shared_ptr<frame>) override {} };

int main() {
    std::shared_ptr<frame> current_frame = std::make_shared<frame>();

    std::shared_ptr<consumer> screen_consumer = std::make_shared<screen_consumer_t>();
    std::shared_ptr<consumer> matrox_consumer = std::make_shared<matrox_consumer_t>();
    std::shared_ptr<consumer> decklink_consumer = std::make_shared<decklink_consumer_t>();

    std::vector<consumer> consumers;
    consumers.push_back(screen_consumer);
    consumers.push_back(matrox_consumer);
    consumers.push_back(decklink_consumer);

    //screen_consumer->draw(current_frame);
    //matrox_consumer->draw(current_frame);
    //decklink_consumer->draw(current_frame);

    for(auto c: consumers) c->draw(current_frame);


}

Edited:

Another example can be a Minimax tree, to avoid cyclic redundancy weak_ptr in conjunction with shared_ptr can be used:

struct node_t
{
    std::unique_ptr<board_t> board_;
    std::weak_ptr<node_t> parent_;
    std::vector<std::shared_ptr<node_t>> children_;
};
3
  • 1
    I don't really see the use of using pointers at all for screen_consumer, etc. instead of just screen_consumer_t screen_consumer; . Also, it's not clear from this example why virtual void draw(frame&) = 0; would not be sufficient. I think any answer will have to involve a real transfer of ownership to a pool of owners with no clear "head" - for example, if the screen_consumer_t et al were actually running in separate threads, and there's also a producer thread that doesn't want to wait for the consumers to be done. Commented Feb 17, 2018 at 1:16
  • @DanielSchepler: IMHO multiple consumers need to be stacked in an STL container so that they can be processed together, so pointer should come easy too.
    – seccpur
    Commented Feb 17, 2018 at 1:26
  • Personally, I agree with Daniel. You are just putting specialized consumers into a vector and then calling their specialized draw. There is no sharing of ownership required here. You could have used unique_ptr instead. You are only "using" these specialized consumers. There is no shared ownership required. Polymorphism still would work.
    – code
    Commented Feb 17, 2018 at 3:23
0

Have you checked these articles about copy-on-write vector:

https://iheartcoding.net/blog/2016/07/11/copy-on-write-vector-in-c/

copy-on-write PIMPL:

https://crazycpp.wordpress.com/2014/09/13/pimplcow/

and generic copy-on-write pointer:

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-on-write

All of them use shared_ptr internally.

0

std::shared_ptr is an implementation of reference counting technique in C++. For use-cases of reference counting see linked wikipedia article. One usage of reference counting is garbage collection in programming languages. So if you decide to write a new programming language with garbage collection in C++ you can implement it with std::shared_ptr, though you will also have to deal with cycles.

-1

Simply put: there isn't really any.

For more detailed explanation, let's turn to formal reasoning. As we all know, C++ is a Turing-complete deterministic language. A popular simple example of equally computationally powerful tool is Brainfuck (often very convenient in establishing Turing-completeness of your favorite language of choice). If we look into Brainfuck's description (which is very small indeed, which makes it very handy for the purposes noted heretofore), we'll soon find out that there is not a single notion of anything resembling shared_ptr in there. So the answer is: no, there is no a real-life example where they would be absolutely required. Everything computable can be done without shared_ptrs.

If we continue the process thoroughly, we'll get rid equally easily of other unnecessary concepts, i.e. unique_ptr, std::unordered_map, exceptions, range-loops and so forth.

1
  • '(and by required, I mean the optimal choice as a smart pointer)' Commented May 8, 2019 at 9:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.