jingyu9575
jingyu9575

Reputation: 531

global static const shared_ptr is strangely stealed and deleted by another shared_ptr's destructor, why?

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:

  1. Compile and run without debugging in Visual Studio 2013, crash (Win8: program stopped working).
  2. Change main to test_2(), crash again.
  3. 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.
  4. Change to test_2 again, set a breakpoint right after the first t is constructed, and look at t in debug window. t[0] has 1 strong ref and t[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?

Upvotes: 1

Views: 677

Answers (1)

Igor Tandetnik
Igor Tandetnik

Reputation: 52471

I think you might be seeing this bug: initializer_list handling broken, destructors of temporary objects called twice. Or this one (could be one and the same): Double delete in initializer_list vs 2013

Upvotes: 1

Related Questions