std::shared_ptr 中的 std::initializer_list 似乎过早地被摧毁了

std::shared_ptr in an std::initializer_list appears to be getting destroyed prematurely

本文关键字:std 摧毁 list ptr shared 中的 initializer      更新时间:2023-10-16

>编辑:这确实是由Visual Studio中的一个错误引起的 - 并且已经修复。更新 2 应用于 Visual Studio 后,此问题不可重现(此处提供候选版本)。我道歉;我以为我已经更新了我的补丁。


我一辈子都无法弄清楚为什么当我在Visual Studio 2013中运行以下代码时会出现seg错误:

#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年底。

奇怪的是,如果我尝试访问列表中的第二项,我会得到预期的行为。例如:

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

考虑到这些事情,我猜这可能是编译器中的一个错误 - 但我想确保我没有忽略为什么这种行为可能被预期的一些解释(例如,也许在 initializer_list s 中如何处理右值)。

这种行为是否可以在其他编译器中重现,或者有人可以解释可能发生的事情吗?

有关分析问题中代码的对象生存期,请参阅原始答案。 这个隔离了错误。


我做了一个最小的复制品。 它更多的代码,但涉及的库代码要少得多。 而且更容易追踪。

#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:CodeSO22924358>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:CodeSO22924358>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:CodeSO22924358>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:CodeSO22924358>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的报告,许多其他人将很乐意投票。

make_shared返回的shared_ptr对象是临时的。 它们将在用于初始化shared_ptr<Base>实例后,在完整表达式结束时销毁。

但是用户对象(Derived1Derived2)的所有权应该共享(或"转移",如果你愿意)到列表中的shared_ptr实例。 这些用户对象应一直存在到 main 结束。

我刚刚使用Visual Studio 2013运行了您问题中的代码,并且没有访问冲突。 奇怪的是,当我跟踪到main()~Base()时,我得到以下输出:

C:CodeSO22924358>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:CodeSO22924358>main
~Base()
Reached end of main
~Base()

这看起来确实不对。

如果我用 GetValue() 的返回值做某事,它是错误的(0而不是1),我得到了访问冲突。 但是,它会在所有跟踪输出之后发生。 而且似乎有些间歇性。

C:CodeSO22924358>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:CodeSO22924358>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>,这是非常非常错误的。