1

Following an interview question about the potential issues with a pointer to a stack variable, I went home and played around.

The behaviour I observe is not what I would expect:

std::shared_ptr<Rectangle> createRectangle(double x, double y)
{
    Rectangle r(x, y);
    return std::make_shared<Rectangle>(r);
}

int main()
{
    auto rect = createRectangle(2, 3);
    std::cout << rect->surface() << " " << &rect << std::endl;
}

Surely enough, rectangle r is a stack variable that should be deleted when going out of scope and my smart pointer become dangling, right? Except that the code above works, and outputs:

6 0019FE98

So I tried with a non smart pointer:

Rectangle* createRectangle(double x, double y)
{
    Rectangle r(x, y);
    return &r;
}

There I got an undefined output value as expected:

-4.62798e+62 0019FEA8

A bit puzzled I went on to read about the issue and realized that there was a secondary problem I didn't think about initially: if the smart pointer goes out of scope before the object, then the object will be deleted (unless an empty deleter is passed when creating the pointer like suggested in the answer). Without the empty deleter, the following code should be problematic I dared to think:

std::shared_ptr<Rectangle> createRectangle(double x, double y)
{
    Rectangle r(x, y);
    if (true) {
        auto rectPtr = std::make_shared<Rectangle>(r);
    }
 
    return std::make_shared<Rectangle>(r);
}

The rectPtr should invoke the deleter on the object after the if block and the object be cleaned from memory, right? But no problem, the output is clean and the value returned.

None of the behaviour I observed with the shared_pointer was what I was expecting.

Can someone please explain me what I am missing? Is my understanding incorrect? Are there some compiler optimizations that are leading the variable pointed to, not to be deleted when we would expect?

Many thanks

2
  • 1
    Your second example uses RVO, in this case the object returned is "pre-allocated" by the caller and will outlive the lifetime duration of the call. It is not a compiler optimization but a language feature. This is not only true for shared_ptr, but also would be true if you would return r by value. Commented Dec 27, 2022 at 10:23
  • 1
    make_shared creates a new shared object. It does not take an existing object and "makes it shared". It's the creational "make" as in "make me a pizza", not the transformational "make" as in "make me a millionaire". Commented Dec 27, 2022 at 10:55

2 Answers 2

3

You don't create a pointer to the variable r, instead you create a copy of it.

Doing

return std::make_shared<Rectangle>(r);

is essentially equivalent to doing

return std::shared_ptr<Rectangle>(new Rectangle(r));

This will copy-construct a new Rectangle object as a shared pointer, and return that shared pointer.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, I see now that make_shared constructs an object, not merely a pointer. Would you say then that the code above is acceptable (if for any reason we had a use for a stack variable before the return)
@sousben Yes your createRectangle function is fine. But since std::make_shared basically call the constructor you could change it to return std::make_shared<Rectangle>(x, y);
1

make_shared internally allocates the object on the heap and passes your stack variable to its constructor. So your shared pointers point to a heap-allocated copy of r.

To create a shared ptr to the stack variable r use this: return shared_ptr(&r);

Edit: Some programmer dude was faster (great name btw), but I'm leaving this up because it contains the correct call you need for your experiment.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.