СТД::общая ПТР в СТД::список инициализатора становится уничтожен преждевременно


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_ptrs, сохраняя их в области действия до конца main.

Как ни странно, если я попытаюсь получить доступ ко второму пункту в списке, я получу ожидаемое поведение. Например:
    auto iter = std::begin(foo) + 1;
    (*iter)->GetValue(); // returns 2
Учитывая эти вещи, я предполагаю, что это может быть ошибка в компиляторе , но я хотел убедиться, что не упустил некоторое объяснение, почему такое поведение может быть ожидаемым (например, возможно, в том, как rvalues обрабатываются в initializer_lists).

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

2 3

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