C++语言在使用列表初始化时是否强制编译器优化?

3

这里是source.cpp的源代码。

#include <iostream>

struct A {
    A(int i) : i(i) { std::cout << this << ": A(int)" << std::endl; }
    A(A const &a) : i(a.i) { std::cout << this << ": A(A const &)" << std::endl; }
    A(A &&a) : i(a.i) { std::cout << this << ": A(A &&)" << std::endl; }
    ~A() { std::cout << this << ": ~A()" << std::endl; }    

  private:
    int i;
};    

int main() {
    std::cout << "#1 :" << std::endl;
    A a1 = 1; // #1, copy-initialization    

    std::cout << "#2 :" << std::endl;
    A a3(1); // #2, direct-initialization    

    std::cout << "#3 :" << std::endl;
    A a4 = {1}; // #3, copy-list-initialization    

    std::cout << "#4 :" << std::endl;
    A a5{1}; // #4, direct-list-initialization    

    std::cout << std::endl;
    return 0;
}

使用以下命令编译上述代码:clang++ -std=c++14 -Wall -fno-elide-constructors -pedantic -o main.exe source.cpp(这里我禁用了构造优化。顺便说一下,我使用的是Clang 3.8.1)。然后,我得到了以下输出:

#1 :
0x61fe40: A(int)
0x61fe48: A(A &&)
0x61fe40: ~A()
#2 :
0x61fe30: A(int)
#3 :
0x61fe28: A(int)
#4 :
0x61fe20: A(int)

0x61fe20: ~A()
0x61fe28: ~A()
0x61fe30: ~A()
0x61fe48: ~A()

令我惊讶的是,尽管两者都使用了复制初始化,但#3没有先调用A::A(int)然后再调用A::A(A &&),就像#1那样。我还用gcc 6.1.0进行了测试。同样的事情发生了。据我所知,列表初始化的常见用法之一是禁止缩小转换。我不知道它与编译优化有任何关系。因此,C++语言是否强制使用编译器优化在使用列表初始化时,或者只是编译器更喜欢这样做,还是其他原因导致了上述行为呢?
1个回答

5
直接初始化和列表初始化在这种情况下都会调用构造函数。通过[dcl.init.list] / 3中的规则进行排除过程。对象或类型为T的引用的列表初始化定义如下:否则,如果T是类类型,则将考虑构造函数。适用的构造函数被枚举,并通过重载决议(13.3,13.3.1.7)选择最佳构造函数。如果需要缩小转换(见下文)来转换任何参数,则程序无效。
重要的是要记住复制初始化和复制列表初始化并不等同,您可以使用复制列表初始化来初始化具有已删除复制和移动构造函数的对象。
struct A
{
  A(int i){}
  A(A const&) =delete;
  A(A&&) =delete;
};    

int main()
{
  A a1 = {1};
  A a2 = 1; // won't compile
}

1
需要注意的是,直接列表初始化和复制列表初始化之间唯一的区别在于后者不会调用explicit构造函数。 - Nicol Bolas
@NicolBolas 我还没有检查过,但是C++17的强制复制省略规则可能也有所不同。 - user657267
保证复制省略(Guaranteed copy elision)的工作方式是声明用于初始化该类型值的 prvalue 不会创建临时对象。一个单独的花括号初始化列表(braced-init-list)永远不会创建临时对象。因此,这些规则不会直接互动。 - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接