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

3

9

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
Monongahela answered 28/7, 2011 at 21:45 Comment(2)
I tried to figure it out here: ideone.com/BUjOwZ. I couldn't come up with an explanation.Murdoch
As others have pointed out, there are plenty of reasons to need a null shared_ptr, just like you have null raw pointers. But the real mystery is why Scott felt the need for it to have a custom deleter.Murdoch
S
22

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.

Saimon answered 28/7, 2011 at 21:48 Comment(7)
The example givien in the quetion, the reason for the null pointer in the constructor call is the use of a custom deleter. The constructor for the smart pointer doesn't have a version that only takes the custom deleter, hence the need for the "hack".Empurple
"std::shared_ptr<Investment> pInt = new Investment;" can't be compiledMonongahela
oops, looks like it's an explicit constructor.Saimon
anyway, because when you reset the pointer the custom deleter will also be reset, right? then the null shared_ptr with a custom deleter completely make no sense in real programming, so weird to have such an example in such a nice bookMonongahela
It's worth noting that shared_ptr has only recently been standardized, and it's possible that it may have had different semantics at some other point in the standardization process (although I kind of doubt it...)Saimon
I assumed I was missing something, I refused to believe Scott could be mistaken! But this is the only explanation I can come up with, +1Murdoch
Note you can now more easily write a null shared_ptr with a custom deleter using std::shared_ptr<Investment> pInv(nullptr, getRidOfInvestment) but I'm not sure why you would want to.Murdoch
H
7

It's the same reason to have a null raw pointer - e.g.

say you have:

typedef std::tr1::shared_ptr<Investment> InvestmentPtr;
map<key,InvestmentPtr> portfolio;
...
get(mykey) {
  iterator it = portfolio.find(mykey);
  if (it == portfolio.end()) 
    return InvestmentPtr();
  else 
    return it->second;
  }
}

This allows you to do:

InvestmentPtr p = get(key);
if (p) ...
Habitforming answered 28/7, 2011 at 21:53 Comment(0)
B
2

There are tons of reasons you might like objects to be default constructible. First and foremost you'd like the smart pointer to be as similar as possible to a raw pointer, and since you can say int * p; (and get an undefined, uninitialized pointer), you can also say shared_ptr<int> p; and get a pointer that doesn't point anywhere (but you get to test it with !).

One of the most compelling reasons is possibly that you can make containers with shared_ptrs, and you can fill the containers without assigning pointees right there and then.

Bravissimo answered 28/7, 2011 at 21:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.