gu1d0
gu1d0

Reputation: 699

two std::shared_ptr from the same raw pointer

I cannot understand why explicit shared_ptr( pointer p ); does not protect the developer from assigning p to another shared_ptr.

My idea is here:

#include <iostream>
#include <memory>

template <typename T>
class ptr
{
    public:
        // ctor a) for lvalues
        explicit ptr(T*& value)  : value_(value) { value=nullptr;  }

        // ctor b) for rvalues pointer reference
        explicit ptr(T*&& value) : value_(value) { }

        ~ptr() { delete value_; }

        // for simplicity this is left public
        T* value_;
};


int main(int argc, char *rgv[])
{
      {
        printf("%10s | %10s | %10s\n", "raw", "p0", "p1");
        // create a raw pointer
        // BAD practice but still could happen 
        int* raw = new int(1);
        printf("%10x | %10x | %10x\n", raw, 0, 0);

        // init p0 from raw pointer ctor (a)
        ptr<int> p0(raw);
        printf("%10x | %10x | %10x\n", raw, p0.value_, 0);

        // init p1 from the same raw poiter again ctor (a)
        ptr<int> p1(raw);
        printf("%10x | %10x | %10x\n", raw, p0.value_, 0);

        // nothing bad happens val was set to nullptr 
        // before p1 construction
      }

    // in order to construct ptr from rvalues 
    // we use ctor (b)
    ptr<int> p2(new int());

      {
        printf("\n");
        // create a raw pointer
        int* raw = new int(1);
        printf("%10x | %10x | %10x\n", raw, 0, 0);

        // init p0 from raw pointer
        std::unique_ptr<int> p0(raw);
        printf("%10x | %10x | %10x\n", raw, p0.get(), 0);

        // init "accidentally" p1 from the same raw poiter again 
        std::unique_ptr<int> p1(raw);
        printf("%10x | %10x | %10x\n", raw, p0.get(), 0);

        // CRASH: double free or corruption
      }

    return 0;
}

Given the above code I got two questions:

1) Did I miss any point implementing ptr this way: in short is there any catch I did not see about ptr(T*& value) and ptr(T*&& value) contructor?

2) What is the rationale behind STL decision not to avoid this possible bug leaving the raw pointer untouched upon shared_ptr construction?

Upvotes: 0

Views: 474

Answers (1)

Mike Seymour
Mike Seymour

Reputation: 254461

Did I miss any point implementing ptr this way?

If a type conversion has to be applied to the pointer, for example

struct Base {/*...*/};
struct Derived : Base {/*...*/};

Derived * d = new Derived;
ptr<Base> p(d);

then a temporary Base pointer will be passed to the rvalue constructor, and d will be left alone.

(You could probably bodge around this with a template constructor, but there might well be further subtelties).

And of course if the user is foolish enough to keep hold of one raw pointer, he may be foolish enough to keep hold of two. This scheme will only nullify one of them.

What is the rationale behind STL decision not to avoid this possible bug leaving the raw pointer untouched upon shared_ptr construction?

Presumably, the principle of least surprise.

Bugs like this are avoided by drawing a distinction between smart and raw pointers, and always treating raw pointers as the dangerous beasts they are. A partial safety-net like this just makes it harder to spot the subtle edge cases waiting to bite you. Idiomatic use of smart pointers, avoiding raw pointers except in unusual situations (where special care is applied), removes the possibility of ownership-related bugs completely.

In C++14, std::make_unique (in addition to the std::make_shared we already have) will mean there's no need for new to appear in your code at all, making it straightforward to avoid any possiblilty of this bug.

Upvotes: 2

Related Questions