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.