This is a follow-up to my previous post about a bug in GCC. I was reading comments, and observed that some readers say that the example I used is rare, or artificial, or that it does not follow “good programming practices”. I want to give you another example.

I will be using a vector of shared_ptr s:

std::vector<std::shared_ptr<R>>;

I do not consider it a good idea in general, but I know people often do it: sometimes because this is simply the right thing to do, and sometimes because this gives them the feeling that they are relieved of any memory management issues.

Also, I will be using std::make_shared to avoid subtle memory leaks, as described in Herb Sutter’s GotW #89. Let’s construct a vector storing two pointers:

std::vector<std::shared_ptr<R>> v {std::make_shared<R>(1), std::make_shared<R>(2)};

Now, what is R ? Something that is a resource-managing class, or at least looks like one, but also something that helps us test exception safety:

struct R { explicit R(int) { acquire_resource(); std::puts("create"); } R(R const&) = delete; // no copying, no moving ~R() { std::puts("destroy"); } };

Function acquire_resource is implemented so that it succeeds upon the first call, and fails upon the second call: this emulates resource exhaustion:

void acquire_resource() { static int resources_exhausted = 0; if (resources_exhausted) throw std::runtime_error("failed"); else ++resources_exhausted; }

And this is enough to re-create our bug in GCC! Here is the full example:

#include <cstdio> #include <memory> #include <stdexcept> #include <vector> void acquire_resource() { static int resources_exhausted = 0; if (resources_exhausted) throw std::runtime_error("failed"); else ++resources_exhausted; } struct R { explicit R(int) { acquire_resource(); std::puts("create"); } R(R const&) = delete; // no copying, no moving ~R() { std::puts("destroy"); } }; int main() { try { std::vector<std::shared_ptr<R>> v {std::make_shared<R>(1), std::make_shared<R>(2)}; } catch (...) {} }

Here again, there is a temporary involved, although we cannot see it explicitly: a temporary of type std::initialized_list<std::shared_ptr<R>> . Similarly, the destructor of a std::shared_ptr<R> is skipped…