Search code examples
c++c++11shared-ptreffective-c++

Why does one need a null shared_ptr and how can it be used?


In Scott Meyers's Effective C++, item 18 Make interfaces easy to use correctly and hard to use incorrectly, he mentioned the null shared_ptr:

std::tr1::shared_ptr<Investment> pInv(static_cast<Investment*>(0), getRidOfInvestment)

and a vogue assignment operation

pInv = ...     //make retVal point to the correct object

In which case one may need to create a null shared_ptr and do assignment later? Why not just create the shared_ptr whenever you have the resources (raw pointer)?

Since Scott Meyers did not show the complete assignment in the previous example, I thought the shared_ptr's assign operator is overloaded that one can do this:

pInv = new Investment;    // pInv will take charge of the pointer
                          // but meanwhile keep the delete function it already had

But I tried with boost's implementation it doesn't work this way. Then what is the sense to have null shared_ptr?

I am almost sure that I am missing something here, someone help me out of it please.

ps. more about the initialization and assignment of a shared_ptr

#include <boost/shared_ptr.hpp>

int main(int argc, char *argv[])
{
    boost::shared_ptr<int> ptr1(new int);
    boost::shared_ptr<int> ptr2;
    ptr2.reset(new int);
    boost::shared_ptr<int> ptr3 = new int;

    return 0;
}

this example can not be compiled by g++ (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2 and the latest boost:

sptr.cpp: In function ‘int main(int, char**)’:
sptr.cpp:8:39: error: conversion from ‘int*’ to non-scalar type ‘boost::shared_ptr<int>’    requested

Solution

  • There is no need to use that hack to get a null (empty) shared_ptr. Simply use the default constructor:

    std::shared_ptr<Investment> pInv; // starts null
    

    To assign a pointer to a shared_ptr, either do it at construction time:

    std::shared_ptr<Investment> pInt(new Investment);
    // not allowed due to explicit annotation on constructor:
    // std::shared_ptr<Investment> pInt = new Investment;
    

    Or use the .reset() function:

    pInt.reset(new Investment);
    

    It's possible that the author of that article may have intended to provide a custom deleter (getRidOfInvestment). However, the deleter function is reset when .reset() is called, or when otherwise the inner pointer is changed. If you want a custom deleter, you must pass it to .reset() upon creation of the shared_ptr.

    One pattern you might want to use to make this more foolproof is a custom creation function:

    class Investment {
    protected:
      Investment();
      // ...
    public:
      static shared_ptr<Investment> create();
    };
    
    shared_ptr<Investment> Investment::create() {
      return shared_ptr<Investment>(new Investment, getRidOfInvestment);
    }
    

    Later:

    shared_ptr<Investment> pInv = Investment::create();
    

    This ensures you will always have the correct destructor function attached to the shared_ptrs created from Investments.