3

編集:これは実際には Visual Studio のバグが原因であり、既に修正されています。Update 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の所有権を取得し、 が終了するまでそれらをスコープ内に保持することを期待していましたmain

奇妙なことに、リストの 2 番目の項目にアクセスしようとすると、期待どおりの動作が得られます。例えば:

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

これらのことを考慮すると、これはコンパイラのバグである可能性があると推測していますが、なぜこの動作が予想されるのかについての説明を見落としていないことを確認したかったのです (たとえば、右辺値がinitializer_lists でどのように処理されるかなど)。

この動作は他のコンパイラで再現できますか、または誰かが何が起こっているのか説明できますか?

4

2 に答える 2

4

質問のコードのオブジェクトの有効期間の分析については、元の回答を参照してください。これにより、バグが分離されます。


最小限の複製を作成しました。それはより多くのコードですが、関与するライブラリ コードははるかに少なくなります。そして、追跡しやすくなります。

#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 で BAD です。

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 に関するレポートを提出すると、他の多くの人が喜んで賛成票を投じます。

于 2014-04-07T23:22:44.103 に答える
1

から返される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()間違っており、アクセス違反が発生します。ただし、すべてのトレース出力の後に発生します。そして、それはやや断続的なようです。01

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>(正しい、そのオブジェクトは a に移動されましたshared_ptr<Base>)、および一致shared_ptr<Base>する は非常に間違っています。

于 2014-04-07T22:43:35.623 に答える