3

the shared_ptr use reference count to determine when to destroy the object. And pls look at this code:

int main() {
    std::shared_ptr<int> pt = std::make_shared<int>(3);
    int *pt2 = pt.get();
    cout << "reference count " << pt.use_count() << endl;
    pt = 0;
    cout << *pt2;

};

after I set pt to 0, the reference count should become 0, and the object should be destroyed. But I can still use pt2 to access it. In my case, the result is correct, but I guess it's just luck. So does it mean that the reference count mechanism still can not make it 100% safe if the programmer want to do some stupid thing?

Ziqi Liu
  • 2,931
  • 5
  • 31
  • 64
  • 6
    Yes, your analysis is quite right. It's C++ : you can still shoot yourself in the foot :) – Olivier Sohn Jul 29 '18 at 07:45
  • 1
    "Smart pointers" aren't pointers, aren't smart, and aren't a fix for incompetence wit pointers. They merely help with automatic release of resources at end of scope – curiousguy Jul 29 '18 at 07:47
  • If the reference count became 0 the object would/should be lost on access through raw pointer, I am glad it isn't like that! – arynaq Jul 29 '18 at 07:48
  • 2
    The library type only implements the boiler-plate one would need to manage it correctly. It doesn't protect against mismanagement. Nothing can protect against the human factor in programming. – StoryTeller - Unslander Monica Jul 29 '18 at 07:48
  • @StoryTeller Some languages do protect against many aspects of the human factor, I'm thinking of Haskell for example. – Olivier Sohn Jul 29 '18 at 07:52
  • @OlivierSohn - Funny you felt the need to qualify it with "many", and not "all", eh? ;) – StoryTeller - Unslander Monica Jul 29 '18 at 07:53
  • @StoryTeller I didn't say "all" because you can write bugs in Haskell, too :) but many bugs you could write in C++ are caught by the Haskell compiler / type system. – Olivier Sohn Jul 29 '18 at 07:55

2 Answers2

5

That is not how shared_ptr works. The count increments when you generate a new shared pointer from the first one. get is just a method to deal with legacy code that can only take a raw pointer. By using get you are subverting the safety of the smart pointer.

In other words, a pointer from get is only good as an observer, not as an owner and for when you really need a raw pointer. If you just need an observer use weak_ptr.

It is true that in principle get could increment the count assuming ownership, but still it wouldn't be clear when to decrement it again. Once the raw pointer is extracted there is no way to get back to the counter of the original.

Stay away from using get unless you know what you are doing.

This is how it works:

#include<iostream>
#include<memory>

using std::cout; using std::endl;

int main(){

    std::shared_ptr<int> pt = std::make_shared<int>(3);
    assert(pt.use_count() == 1);
    std::shared_ptr<int> pt2 = pt;
    assert(pt.use_count() == 2);
    assert(pt2.use_count() == 2);
    pt.reset(); // or pt = 0;
    assert(pt.use_count() == 0);
    assert(pt2.use_count() == 1);
    assert(*pt2 == 3);
    assert(!pt);

    return 0;
}
alfC
  • 14,261
  • 4
  • 67
  • 118
5

after I set pt to 0, the reference count should become 0, and the object should be destroyed

The reference count did become zero and the object was destroyed.

Replace int with your own class MyInt to see the constructors and destructors being called...

class MyInt
{
private:
   int val;

public:
   MyInt() : val(0)          { std::cout << "default c'tor called" << std::endl; }
   MyInt(int rhs) : val(rhs) { std::cout << "c'tor (" << rhs << ") called" << std::endl; }
   ~MyInt()                  { std::cout << "d'tor called" << std::endl; }
   int getval (void)         { return val; }
};

… then update main()

int main()
{
   std::shared_ptr<MyInt> pt = std::make_shared<MyInt>(3);

   MyInt* pt2 = pt.get();

   std::cout << "reference count " << pt.use_count() << std::endl;

   pt = 0;

   std::cout << pt2->getval() << std::endl;

   return 0;
}

The output will (perhaps) look like this...

c'tor (3) called
reference count 1
d'tor called
3

runnable sample

The fact that the last line of output is the value three (if it is three) is not evidence that the object was not deleted.

Dereferencing a pointer that points to a deleted object is undefined behaviour so anything can happen.

Frank Boyne
  • 4,400
  • 23
  • 30