Nathan Hoad

shared_ptr Fun

April 11, 2021

The other day I was smashing some APIs together, one with a shared_ptr, the other with a raw pointer, and I did a bad job of it in a non-obvious way. Here’s a trivialized example of the broken code:

#include <memory>
#include <iostream>
#include <string>

struct thing {
    thing(std::string *v) : value(v) {};
    std::string *value;
    
    void process() {
        std::cout << *value << std::endl;
    }
};

int main()
{
    thing t(std::make_shared<std::string>("cool thing").get());
    std::make_shared<std::string>("haha WHAT");
    t.process();

    return 0;
}

thing::value contains a pointer to a string “cool thing”, and then we allocate “haha WHAT” into a shared_ptr that gets thrown away immediately. What does process print?

$ g++ ugh.cpp && ./a.out
haha WHAT

Well clearly that output is wrong, it should be “cool thing”. What’s happening here is that thing's constructor is receiving the raw pointer to our cool thing string, and then at the end of the expression the shared_ptr that owned it is destructed. So thing::value now contains a dead pointer.

When I was hitting this code it was causing segfaults, but I can’t be bothered coming up with a complicated enough example to reproduce that. I like my example more anyway because it’s even harder to debug if you don’t know what you’re looking for.

The way to fix this? Used shared_ptr everywhere, raw pointers are (obviously) bad.

In hindsight this is a really obvious bug, but you know. That’s how how they start out.