std::initializer_list 中的 std::shared_ptr 似乎过早地被破坏
Posted
技术标签:
【中文标题】std::initializer_list 中的 std::shared_ptr 似乎过早地被破坏【英文标题】:std::shared_ptr in an std::initializer_list appears to be getting destroyed prematurely 【发布时间】:2014-04-07 22:35:35 【问题描述】:编辑:这确实是由 Visual Studio 中的一个错误引起的 - 它已经被修复。 将 Update 2 应用到 Visual Studio (release candidate available here) 后,该问题无法重现。我道歉;我以为我的补丁是最新的。
我一生都无法弄清楚为什么在 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
考虑到这些事情,我猜这可能是编译器中的一个错误——但我想确保我没有忽略一些解释为什么会出现这种行为(例如,可能是如何处理右值) initializer_list
s)。
这种行为在其他编译器中是否可以重现,或者有人可以解释可能发生的情况吗?
【问题讨论】:
如果您使用析构函数(例如使用std::cerr
或OutputDebugString
),您是否会遇到同样的失败?如果行为仍然存在,析构函数是否提前运行?
似乎与 g++ 4.7 一起工作正常。可能会增加您的分析,即编译器可能存在问题。
请注意,shared_ptr
可能会被销毁。 std::shared_ptr<Base>
对象通过转换临时 std::shared_ptr<Derived1>
(和 2)对象来初始化。但这不应该导致Derived1
(和2)对象本身的死亡,因为在shared_ptr
转换期间每个对象的引用计数应该达到2。或者也许移动构造函数用于转换并且引用计数被盗。
并不是说这有多大帮助,但同样可以在 clang++ 上正常工作(调试和所有); (Apple LLVM 版本 5.1 (clang-503.0.38)(基于 LLVM 3.4svn))。 g++ 4.8 也是如此。
感谢其他编译器的所有信息。 @BenVoigt 这是有道理的。我曾尝试使用调试器进入一些 MSFT 标头以更好地了解正在发生的事情,但很快就迷失了自己。作为一个额外的数据点:如果我像这样在列表中创建 4 个项目,则访问前 两个 会导致访问冲突。
【参考方案1】:
请参阅original answer 以分析问题中代码的对象生命周期。这个隔离了错误。
我做了一个最小的复制。它的代码更多,但涉及的库代码要少得多。并且更容易追踪。
#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 的报告,许多其他人将很乐意投票。
【讨论】:
好主意,设置代码以在最少的库参与的情况下重现问题。 @Lilshieste:它可以很容易地放入调试输出并查看(错误)事件的确切顺序。【参考方案2】:从make_shared
返回的shared_ptr
对象是临时对象。在用于初始化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>
,这是非常非常错误的。
【讨论】:
感谢您的帮助。我将继续创建 Connect 票证,以获得 Microsoft 对此的一些反馈。 @Lilshieste:请检查我的全新答案,它重现了没有std::shared_ptr
的问题。以上是关于std::initializer_list 中的 std::shared_ptr 似乎过早地被破坏的主要内容,如果未能解决你的问题,请参考以下文章
std::initializer_list 返回值的生命周期
为啥 `std::initializer_list` 不提供下标运算符?
为啥 std::min(std::initializer_list<T>) 按值接受参数?
为啥 std::initializer_list 不是内置语言?