Search code examples
c++shared-ptr

Shared_ptr cannot be initialized in constructor if `this` involved


I have some code structured like this:

#include <memory>
#include <vector>

using namespace std;
struct demo {
    vector<shared_ptr<demo>> foo{shared_ptr<demo>(this)};
    void append(){
        foo.push_back(make_shared<demo>());
    }
};

demo dummy(/* accept some arguments */){
    demo a{};
    a.append();
    return a;
}

int main()
{
    demo bar=dummy();
    return 0;
}

The dummy function just do some process based on its argument on a newly constructed demo instance and return it. (I'm pretty sure that the above is enough to expose the problem)

It compiles well with no error, however it won't terminate it self. (I'm using g++ of MinGW on Win7).

Then I try to work around. I added a copy constructor (as well as a explicit default constructor), and then use new to only keep a pointer to demo in main function. So the code will be something like:

...
struct demo {
    vector<shared_ptr<demo>> foo {shared_ptr<demo>(this)};
    void append() {
        foo.push_back(make_shared<demo>());
    }
    demo(){
    };
    demo(const demo& instance): foo(instance.foo) {
    }
};
...
int main()
{
//    demo bar=dummy();
    demo* bar=new demo(dummy());
    delete bar;
    return 0;
}

Then it just worked. But still I wonder the mechanism behind those lines. Would you just explain what's difference between the new constructed and normally declared variable?

EDIT: So finally is there anyway to push_back this into a vector as struct member in its constructor? (For the reason, it's because the rest of my vector stores all shared_ptr except the first one, in other words, even if the vector is pop_backed to be empty, it could still return this)


Solution

  • Here is a more minimal example, that reproduces your problem:

    struct demo {
        shared_ptr<demo> foo{this};
    };
    
    int main()
    {
        demo bar;
    }
    

    The problem is that you store a shared pointer to this, in a member. So, when a demo instance is destroyed, the shared pointer is decremented, and when the shared pointer is decremented to zero, then it will delete the object again, causing undefined behaviour.

    Even if that wasn't the problem, there is still the issue that bar is an automatic object. And you may not have shared pointers to automatic objects. But bar does create a shared pointer to itself.

    demo* bar=new demo(dummy());
    delete bar;
    

    This is better, because now the object (pointed by bar) has dynamic storage, so you may have a shared pointer to it. You must of course fix dummy to not create an automatic demo instance as well.

    Also, you may not explicitly delete an instance of demo because there is a shared pointing to itself inside. The "correct" way to dispose an instance of demo would be to remove the shared pointer, which would then take care of the deletion:

    demo* bar=new demo(dummy());
    bar->foo.clear();      // this is for your vector version
    //bar->foo = nullptr;  // this is for my minimized example above
    // object pointed by bar no longer exists
    // (unless the shared pointer was copied and exists elsewhere)
    

    However, I find this design very confusing, and would not recommend it. You should reconsider why you store a shared pointer to this.