How do shared pointers work?

前端 未结 5 1861
无人共我
无人共我 2020-12-02 13:34

How do shared pointers know how many pointers point to that object? (shared_ptr, in this case)

5条回答
  •  广开言路
    2020-12-02 13:45

    I generally agree with James McNellis's answer. However there's one more point that should be mentioned.

    As you may know, shared_ptr may also be used when the type T is not fully defined.

    That is:

    class AbraCadabra;
    
    boost::shared_ptr myPtr;
    // ...
    

    This will compile & work. Unlike many other implementations of smart pointers, which actually demand the encapsulated type to be fully defined in order to use them. This is related to the fact that the smart pointer is supposed to know to delete the encapsulated object when it's no more referenced, and in order to delete an object one must know what it is.

    This is achieved by the following trick: shared_ptr actually consists of the following:

    1. An opaque pointer to the object
    2. Shared reference counters (what James McNellis described)
    3. A pointer to the allocated factory that knows how to destroy your object.

    The above factory is a helper object with a single virtual function, which is supposed to delete your object in a correct way.

    This factory is actually created when you assign a value to your shared pointer.

    That is, the following code

    AbraCadabra* pObj = /* get it from somewhere */;
    myPtr.reset(pObj);
    

    This is where this factory is allocated. Note: the reset function is actually a template function. It actually creates the factory for the specified type (type of the object passed as a parameter). This is where your type should be fully defined. That is, if it's still not defined - you'll get a compilation error.

    Note also: if you actually create an object of a derived type (derived from AbraCadabra), and assign it to the shared_ptr - it will be deleted in a correct way even if your destructor is not virtual. The shared_ptr will always delete the object according to the type that is sees in reset function.

    So that shared_ptr is a pretty sophisticated variant of a smart pointer. It gives an awesome flexibility. However you should know that this flexibility comes at a price of an extremely bad performance compared to other possible implementations of the smart pointer.

    On the other hand - there're so-called "intrusive" smart pointers. They don't have all that flexibility, however in contrast they give the best performance.

    Pros of shared_ptr compared to inrusive smart pointers:

    • Very flexible usage. Only have to define the encapsulated type when assigning it to the shared_ptr. This is very valuable for big projects, reduces dependencies greatly.
    • The encapsulated type doesn't have to have a virtual destructor, still polymorphic types will be deleted correctly.
    • Can be used with weak pointers.

    Cons of shared_ptr compared to inrusive smart pointers:

    1. Very barbaric performance and waste of heap memory. On assignment allocates 2 more objects: reference counters, plus the factory (waste of memory, slow). This however happens only on reset. When one shared_ptr is assigned to another one - nothing more is allocated.
    2. The above may throw an exception. (out-of-memory condition). In contrast intrsusive smart pointers may never throw (apart from process exceptions related to invalid memory access, stack overflow and etc.)
    3. Deletion of your object is also slow: need to deallocate another two structs.
    4. When working with intrusive smart pointers you may freely mix smart pointers with raw ones. This is ok because the actual reference counting resides inside the object itself, which is single. In contrast - with shared_ptr you may not mix with raw pointers.
        AbraCadabra* pObj = /* get it from somewhere */;
        myPtr.reset(pObj);
        // ...
        pObj = myPtr.get();
        boost::shared_ptr myPtr2(pObj); // oops
    

    The above will crash.

提交回复
热议问题