СТД::общая ПТР в СТД::список инициализатора становится уничтожен преждевременно
Edit: это действительно вызвано ошибкой в Visual Studio - и она уже была исправлена. проблема не воспроизводится после применения Update 2 к Visual Studio (релиз-кандидат доступен здесь ). Я извиняюсь; я думал, что был в курсе моих патчей.
Я никак не могу понять, почему я получаю ошибку seg при запуске следующего кода в 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;
}
Я ожидал, что initializer_list
станет владельцем созданных shared_ptr
s, сохраняя их в области действия до конца main
.
auto iter = std::begin(foo) + 1;
(*iter)->GetValue(); // returns 2
Учитывая эти вещи, я предполагаю, что это может быть ошибка в компиляторе , но я хотел убедиться, что не упустил некоторое объяснение, почему такое поведение может быть ожидаемым (например, возможно, в том, как rvalues обрабатываются в initializer_list
s).
Воспроизводимо ли это поведение в других компиляторах, или кто-нибудь может объяснить, что происходит?
2 ответа:
Смотритеисходный ответ для анализа времени жизни объекта кода в вопросе. Этот изолирует жука.
Я сделал минимальную репродукцию. Это больше кода,но гораздо меньше библиотечного кода. И его легче отследить.
#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; }
Выход плохой на обоих 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
И 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
Определенно ошибка компилятора,и довольно серьезная. Если вы подадите отчет на Connect I и многие другие будут рады сообщить об этом.
Объекты
shared_ptr
, возвращаемые изmake_shared
, являются временными. Они будут уничтожены в конце полного выражения, после использования для инициализации экземпляровshared_ptr<Base>
.Но владение объектами пользователя (
Derived1
иDerived2
) должно быть общим (или "передано", если хотите) экземплярамshared_ptr
в списке. Эти пользовательские объекты должны жить до концаmain
.Я только что прогнал код из вашего вопроса с помощью Visual Studio 2013 и не получил нарушения доступа. Как ни странно, когда Я прослеживаю до
main()
и~Base()
, я получаю следующий результат:Это действительно выглядит неправильно.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()
И если я делаю что-то с возвращаемым значением
GetValue()
, это неправильно (0
вместо1
), и я получаю нарушение доступа. Однако это происходит после всех выходных данных трассировки. И это кажется несколько прерывистым.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()
Вот окончательная версия кода, с которым я работаю:
#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; }
Пошаговое выполнение показывает, что деструкторы вызываются сразу после инициализатора построение списка для
shared_ptr<Derived1>
(правильно, его объект был перемещен вshared_ptr<Base>
) и совпадениеshared_ptr<Base>
, что очень и очень неправильно.