Редактировать: Это действительно вызвано ошибкой в Visual Studio — и это уже исправлено. Проблема не воспроизводится после применения Обновление 2 в Visual Studio (релиз кандидата доступен здесь). Приношу извинения; Я думал, что был в курсе моих патчей.
Я не могу понять, почему у меня возникает ошибка сегмента, когда я запускаю следующий код в 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
Учитывая эти вещи, я предполагаю, что это может быть ошибкой в компиляторе, но я хотел убедиться, что не пропустил какое-то объяснение того, почему такое поведение можно ожидать (например, в том, как обрабатываются значения r в initializer_list
с).
Воспроизводимо ли это поведение в других компиляторах, или кто-то может объяснить, что может происходить?
Увидеть оригинальный ответ для анализа времени жизни объекта кода в вопросе. Этот изолирует ошибку.
Я сделал минимальную репродукцию. Это больше кода, но гораздо меньше библиотечного кода. И легче отследить.
#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>
что очень и очень неправильно.