问题
Edit: This is indeed caused by a bug in Visual Studio - and it has already been fixed. The issue is not reproducible after applying Update 2 to Visual Studio (release candidate available here). I apologize; I thought I was up to date with my patches.
I can't for the life of me figure out why I get a seg fault when I run the following code in Visual Studio 2013:
#include <initializer_list>
#include <memory>
struct Base
{
virtual int GetValue() { return 0; }
};
struct Derived1 : public Base
{
int GetValue() override { return 1; }
};
struct Derived2 : public Base
{
int GetValue() override { return 2; }
};
int main()
{
std::initializer_list< std::shared_ptr<Base> > foo
{
std::make_shared<Derived1>(),
std::make_shared<Derived2>()
};
auto iter = std::begin(foo);
(*iter)->GetValue(); // access violation
return 0;
}
I was expecting the initializer_list
to take ownership of the created shared_ptr
s, keeping them in scope until the end of main
.
Oddly enough, if I try to access the second item in the list, I get the expected behavior. For example:
auto iter = std::begin(foo) + 1;
(*iter)->GetValue(); // returns 2
Considering these things, I'm guessing this may be a bug in the compiler - but I wanted to make sure I wasn't overlooking some explanation for why this behavior might be expected (e.g., maybe in how rvalues are handled in initializer_list
s).
Is this behavior reproducible in other compilers, or can someone explain what might be happening?
回答1:
See the original answer for analysis of object lifetimes of the code in the question. This one isolates the bug.
I made a minimal reproduction. It's more code, but a lot less library code involved. And easier to trace.
#include <initializer_list>
template<size_t N>
struct X
{
int i = N;
typedef X<N> self;
virtual int GetValue() { return 0; }
X() { std::cerr << "X<" << N << ">() default ctor" << std::endl; }
X(const self& right) : i(right.i) { std::cerr << "X<" << N << ">(const X<" << N << "> &) copy-ctor" << std::endl; }
X(self&& right) : i(right.i) { std::cerr << "X<" << N << ">(X<" << N << ">&& ) moving copy-ctor" << std::endl; }
template<size_t M>
X(const X<M>& right) : i(right.i) { std::cerr << "X<" << N << ">(const X<" << M << "> &) conversion-ctor" << std::endl; }
template<size_t M>
X(X<M>&& right) : i(right.i) { std::cerr << "X<" << N << ">(X<" << M << ">&& ) moving conversion-ctor" << std::endl; }
~X() { std::cerr << "~X<" << N << ">(), i = " << i << std::endl; }
};
template<size_t N>
X<N> make_X() { return X<N>{}; }
#include <iostream>
int main()
{
std::initializer_list< X<0> > foo
{
make_X<1>(),
make_X<2>(),
make_X<3>(),
make_X<4>(),
};
std::cerr << "Reached end of main" << std::endl;
return 0;
}
The output is BAD on both x64:
C:\Code\SO22924358>cl /EHsc minimal.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
minimal.cpp
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:minimal.exe
minimal.obj
C:\Code\SO22924358>minimal
X<1>() default ctor
X<0>(X<1>&& ) moving conversion-ctor
X<2>() default ctor
X<0>(X<2>&& ) moving conversion-ctor
X<3>() default ctor
X<0>(X<3>&& ) moving conversion-ctor
X<4>() default ctor
X<0>(X<4>&& ) moving conversion-ctor
~X<0>(), i = 2
~X<2>(), i = 2
~X<0>(), i = 1
~X<1>(), i = 1
Reached end of main
~X<0>(), i = 4
~X<0>(), i = 3
~X<0>(), i = 2
~X<0>(), i = 1
and x86:
C:\Code\SO22924358>cl /EHsc minimal.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
minimal.cpp
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:minimal.exe
minimal.obj
C:\Code\SO22924358>minimal
X<1>() default ctor
X<0>(X<1>&& ) moving conversion-ctor
X<2>() default ctor
X<0>(X<2>&& ) moving conversion-ctor
X<3>() default ctor
X<0>(X<3>&& ) moving conversion-ctor
X<4>() default ctor
X<0>(X<4>&& ) moving conversion-ctor
~X<0>(), i = 2
~X<2>(), i = 2
~X<0>(), i = 1
~X<1>(), i = 1
Reached end of main
~X<0>(), i = 4
~X<0>(), i = 3
~X<0>(), i = 2
~X<0>(), i = 1
Definitely a compiler bug, and a pretty severe one. If you file a report on Connect I and many others will be happy to upvote.
回答2:
The shared_ptr
objects returned from make_shared
are temporaries. They will be destroyed at the end of the full-expression, after being used to initialize shared_ptr<Base>
instances.
But ownership of the user objects (the Derived1
and Derived2
) should be shared (or "transferred" if you like) to the shared_ptr
instances in the list. Those user objects should live until the end of main
.
I just ran the code from your question using Visual Studio 2013 and got no access violation. Oddly, when I trace to main()
and ~Base()
, I get the following output:
C:\Code\SO22924358>cl /EHsc main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
main.cpp
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:main.exe
main.obj
C:\Code\SO22924358>main
~Base()
Reached end of main
~Base()
That does look wrong.
And if I do something with the return value of GetValue()
, it is wrong (0
instead of 1
) and I get the access violation. It occurs after all tracing output, however. And it seems somewhat intermittent.
C:\Code\SO22924358>cl /Zi /EHsc main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
main.cpp
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:main.exe
/debug
main.obj
C:\Code\SO22924358>main
~Base()
GetValue() returns 0
Reached end of main
~Base()
Here's the final version of the code I'm working with:
#include <initializer_list>
#include <memory>
#include <iostream>
struct Base
{
virtual int GetValue() { return 0; }
~Base() { std::cerr << "~Base()" << std::endl; }
};
struct Derived1 : public Base
{
int GetValue() override { return 1; }
};
struct Derived2 : public Base
{
int GetValue() override { return 2; }
};
int main()
{
std::initializer_list< std::shared_ptr<Base> > foo
{
std::make_shared<Derived1>(),
std::make_shared<Derived2>()
};
auto iter = std::begin(foo);
std::cerr << "GetValue() returns " << (*iter)->GetValue() << std::endl; // access violation
std::cerr << "Reached end of main" << std::endl;
return 0;
}
Stepping through shows that destructors are called immediately after initializer list construction for shared_ptr<Derived1>
(correct, its object has been moved to a shared_ptr<Base>
), and the matching shared_ptr<Base>
, which is very very wrong.
来源:https://stackoverflow.com/questions/22924358/stdshared-ptr-in-an-stdinitializer-list-appears-to-be-getting-destroyed-prem