I was writting a file manager and saw reproducible crashes when I open a folder twice. To mininize the related code:
#include <vector>
#include <memory>
#include <boost/smart_ptr.hpp>
namespace myns {
using std::shared_ptr;
}
class Base {
public:
virtual ~Base() {}
};
class Derived_1 : public Base {
public:
~Derived_1() {} // breakpoint 1
};
myns::shared_ptr<Derived_1> Derived_1_ptr() {
static const myns::shared_ptr<Derived_1> r{new Derived_1};
return r;
}
class Derived_2 : public Base {};
myns::shared_ptr<Derived_2> Derived_2_ptr() {
static const myns::shared_ptr<Derived_2> r{new Derived_2};
return r;
}
std::vector<myns::shared_ptr<Base>> all_derived_ptrs() {
return{Derived_1_ptr(), Derived_2_ptr()}; // no breakpoint
}
void test_1() {
all_derived_ptrs(); // breakpoint 2
all_derived_ptrs(); // breakpoint 3
}
void test_2() {
{
std::vector<myns::shared_ptr<Base>> t{Derived_1_ptr(), Derived_2_ptr()};
}
{
std::vector<myns::shared_ptr<Base>> t{Derived_1_ptr(), Derived_2_ptr()};
}
}
int main() {
test_1();
return 0;
}
Derived_1_ptr() and Derived_2_ptr() are in fact global variables that solve the initialization order problem.
With these code:
- Compile and run without debugging in Visual Studio 2013, crash (Win8: program stopped working).
- Change main to test_2(), crash again.
- Change back to test_1, set 3 breakpoint as above and debug. It first break at #2, then #1, then #3, then a dialog popup saying "has triggered a breakpoint" and it break in ntdll while "next statement" is at
no breakpoint
. - Change to test_2 again, set a breakpoint right after the first
t
is constructed, and look att
in debug window.t[0]
has 1 strong ref andt[1]
has 2 strong refs.
So I can infer that when the first vector is constructed, Derived_2_ptr()
is correctly copied (++reference_count), while Derived_1_ptr()
seems to be stealed (moved?) from the static const r (reference_count is still 1). When the first vector is destroyed Derived_2_ptr()
correctly performs --reference_count and is still alive, but Derived_1_ptr()
loses its only reference and dies before the second vector is created (it is a global static variable!).
Why? Can move constructor be applied to static const
things?
I try changing the return type of Derived_x_ptr()
to const myns::shared_ptr<Derived_x>&
or myns::shared_ptr<Base>
, and both work correctly.
Also, if I remove Derived_2_ptr() and make all_derived_ptrs return{Derived_1_ptr(), nullptr}
, Derived_1_ptr() magically works.
If I use boost::shared_ptr instead, the problem is hidden (no program stopped dialog) but ~Derived_1() is still called too early.
What is going on here? Does standard allows this kind of move? Is it a Microsoft-specific thing, or a VS2013-specific thing (I don't have another C++11 compiler)? Any ideas?