【发布时间】:2017-09-23 06:40:48
【问题描述】:
这与question I asked earlier today 非常相似。但是,我在那个问题中引用的例子是不正确的;错误地,我查看了错误的源文件,而不是实际出现我描述的错误的源文件。无论如何,这是我的问题的一个例子:
struct base { };
struct child1 : base { };
struct child2 : base { };
child1 *c1;
child2 *c2;
// Goal: iterate over a few derived class pointers using a range-based for loop.
// initializer_lists are convenient for this, but we can't just use { c1, c2 }
// here because the compiler can't deduce what the type of the initializer_list
// should be. Therefore, we have to explicitly spell out that we want to iterate
// over pointers to the base class.
for (auto ptr : std::initializer_list<base *>({ c1, c2 }))
{
// do something
}
这是我的应用程序中一些细微的内存损坏错误的站点。经过一番试验,我发现将上面的内容更改为以下内容可以让我观察到的崩溃消失:
for (auto ptr : std::initializer_list<base *>{ c1, c2 })
{
// do something
}
请注意,唯一的变化是在大括号初始化列表周围增加了一组括号。我仍在尝试围绕所有形式的初始化。我是否正确地推测在第一个示例中,内部大括号初始化程序是一个临时的,由于它是 std::initializer_list 复制构造函数的参数,它的生命周期不会延长循环的生命周期? Based on this previous discussion of what the equivalent statement for the range-based for is,我觉得是这样的。
如果这是真的,那么第二个示例是否成功,因为它使用std::initializer_list<base *> 的直接列表初始化,因此临时花括号列表的内容将其生命周期延长到循环的持续时间?
编辑:经过更多的工作,我现在有了一个独立的示例来说明我在应用程序中看到的行为:
#include <initializer_list>
#include <memory>
struct Test
{
int x;
};
int main()
{
std::unique_ptr<Test> a(new Test);
std::unique_ptr<Test> b(new Test);
std::unique_ptr<Test> c(new Test);
int id = 0;
for(auto t : std::initializer_list<Test*>({a.get(), b.get(), c.get()}))
t->x = id++;
return 0;
}
如果我在 macOS 上使用 Apple LLVM 版本 8.1.0 (clang-802.0.42) 编译它,如下所示:
clang++ -O3 -std=c++11 -o crash crash.cc
然后,当我运行生成的程序时,它会因段错误而死掉。使用较早版本的 clang (8.0.0) 不会出现此问题。同样,我在 Linux 上使用 gcc 进行的测试也没有任何问题。
我正在尝试确定此示例是否说明了由于我上面提到的临时生命周期问题导致的未定义行为(在这种情况下,我的代码将是错误的,并且更新的 clang 版本将被证明是正确的),或者这是否是可能只是这个特定 clang 版本中的一个错误。
编辑 2: The test is live here。看起来行为变化发生在主线 clang v3.8.1 和 v3.9.0 之间。在 v3.9 之前,程序的有效行为只是int main() { return 0; },这是合理的,因为代码没有副作用。然而,在以后的版本中,编译器似乎优化了对operator new 的调用,但在循环中保留了对t->x 的写入,因此试图访问未初始化的指针。我想这可能指向编译器错误。
【问题讨论】:
-
我看不出代码有任何问题。即使使用 std::initializer 列表不是一个好主意,它也没有什么问题。并且在范围基础 for 循环期间初始化列表中的数据的生命周期是有效的。因此,我看不出有任何理由将列表用作问题的根源。顺便说一句:如果你简单地用
Base* ptr1 = new Child1生成你的指针......你可以直接写for( auto ptr: { ptr1, ptr2 })...因为所有的ptr都有相同的类型(基数)所以类型推导没有问题。如果使用基类型的智能指针也是一样。
标签: c++ c++11 language-lawyer clang++ list-initialization