C++ 在基于范围的for循环中使用带括号的初始值设定项列表是否不安全?

C++ 在基于范围的for循环中使用带括号的初始值设定项列表是否不安全?,c++,c++11,language-lawyer,clang++,list-initialization,C++,C++11,Language Lawyer,Clang++,List Initialization,这非常类似于a。然而,我在该问题中引用的例子是不正确的;由于错误,我查看的源文件是错误的,而不是我描述的实际错误的源文件。无论如何,这里有一个我的问题的例子: 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

这非常类似于a。然而,我在该问题中引用的例子是不正确的;由于错误,我查看的源文件是错误的,而不是我描述的实际错误的源文件。无论如何,这里有一个我的问题的例子:

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
}
如果我使用苹果LLVM版本8.1.0(clang-802.0.42)在macOS上编译此文件,如下所示:

clang++ -O3 -std=c++11 -o crash crash.cc
然后,当我运行程序时,结果程序将以segfault终止。使用早期版本的clang(8.0.0)不会出现此问题。同样,我在Linux上使用gcc进行的测试也没有任何问题

我试图确定这个示例是否说明了由于我上面提到的临时生存期问题而导致的未定义的行为(在这种情况下,我的代码可能是错误的,更新的clang版本将被证明是正确的),或者这是否可能只是这个特定clang版本中的一个bug


编辑2:。看起来行为上的变化发生在主线ClangV3.8.1和v3.9.0之间。在v3.9之前,程序的有效行为只是
int main(){return 0;}
,这是合理的,因为代码没有副作用。但是,在以后的版本中,编译器似乎优化了对
操作符new
的调用,但将对
t->x
的写入保留在循环中,因此尝试访问未初始化的指针。我想这可能指向一个编译器错误。

我将分两部分回答:元答案和具体答案

一般(和更相关的)答案是:除非必须,否则不要使用
初始值设定项列表。
使用括号内的初始值设定项列表是否不安全

当然是。通常,您不应该自己构造初始值设定项列表。这是语言和标准库之间的一个难看的细节。你应该忽略它的存在,除非你一定要使用它,即使这样做,因为其他代码造就了你,不要把自己放进需要成为语言律师才能爬出来的洞里。有很多精巧的文字,如“代理”、“提供访问”(相对于“是”)、“可以实现为”(但也可以不实现)和“之后不保证存在”。啊

更具体地说,您使用的是基于范围的for循环。是什么让你想显式地实例化一个
std::initializer\u list
?以下内容有什么问题:

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 : {a.get(), b.get(), c.get()} )
         t->x = id++;
}
结构测试{int x;}; int main(){ std::唯一测试a(新测试); 标准:唯一性试验b(新试验); std::唯一测试c(新测试); int id=0; for(auto t:{a.get(),b.get(),c.get()}) t->x=id++; }
??这就是你的例子应该写的方式

现在,您可能会问:“但是括号中的初始值设定项列表是什么类型的?”

对此,我想说:

  • 跟你有什么关系?意图是明确的,没有理由相信此处会发生任何SEGFULT。如果这个代码不起作用,你可以(也应该)直接向标准委员会投诉这种语言是如何脑死亡的
  • 我不知道。或者我更确切地说,除非我真的必须知道,否则我不知道
  • (好的,就在你和我之间-这是
    std::initializer\u list&&
    ,但实际上,忘了这个事实。)
  • 关于代码的特定最终版本 你的循环

    for(auto t : std::initializer_list<Test*>({a.get(), b.get(), c.get()})) { t->x = id++; }
    
    for(auto t:std::initializer_list({a.get(),b.get(),c.get()})){t->x=id++;}
    
    是指以下代码:

    auto && range = std::initializer_list<Test*>({a.get(), b.get(), c.get()});
    for (auto begin = std::begin(range), end = range.end(); begin != end; ++begin) {
        auto t = *begin;
        t->x = id++;
    }
    
    auto&&range=std::initializer_list({a.get(),b.get(),c.get()});
    对于(自动开始=标准::开始(范围),结束=范围。结束();开始!=结束;++开始){
    自动t=*开始;
    t->x=id++;
    }
    
    初始值设定项列表定义说明:

    在原始初始值设定项列表对象的生存期结束后,不能保证基础数组存在


    我不是语言律师,但我的猜测如下:似乎您正在用一个内部初始值设定项列表初始化外部初始值设定项列表,因此没有得到外部初始值设定项列表构造函数之外的内部初始值设定项列表的保证生存期延长。因此,是否让它保持活动状态取决于编译器的突发奇想,完全有可能不同版本的编译器在这方面表现不同。

    我看不出代码有任何问题。即使使用std::initializer list不是一个好主意,它也没有什么问题。并且在循环的范围内,来自初始值设定项列表的数据的生存期是有效的。所以我看不出有什么理由把这个列表作为你问题的根源。顺便说一句:如果你用
    Base*ptr1=newchild1
    简单地生成指针。。。您可以直接为(自动ptr:{ptr1,ptr2})编写
    由于所有ptr都具有相同的类型(基),因此类型推断没有问题。如果使用具有基类型的智能指针,则相同。
    for(auto t : std::initializer_list<Test*>({a.get(), b.get(), c.get()})) { t->x = id++; }
    
    auto && range = std::initializer_list<Test*>({a.get(), b.get(), c.get()});
    for (auto begin = std::begin(range), end = range.end(); begin != end; ++begin) {
        auto t = *begin;
        t->x = id++;
    }