std :: shared_ptr в списке std :: initializer_list, похоже, преждевременно уничтожается

Редактировать: Это действительно вызвано ошибкой в ​​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_ptrs, сохраняя их в поле зрения до конца main,

Как ни странно, если я пытаюсь получить доступ ко второму элементу в списке, я получаю ожидаемое поведение. Например:

    auto iter = std::begin(foo) + 1;
(*iter)->GetValue(); // returns 2

Учитывая эти вещи, я предполагаю, что это может быть ошибкой в ​​компиляторе, но я хотел убедиться, что не пропустил какое-то объяснение того, почему такое поведение можно ожидать (например, в том, как обрабатываются значения r в initializer_listс).

Воспроизводимо ли это поведение в других компиляторах, или кто-то может объяснить, что может происходить?

3

Решение

Увидеть оригинальный ответ для анализа времени жизни объекта кода в вопросе. Этот изолирует ошибку.


Я сделал минимальную репродукцию. Это больше кода, но гораздо меньше библиотечного кода. И легче отследить.

#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 и многие другие, с радостью ответим.

4

Другие решения

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>что очень и очень неправильно.

1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector