GCC和clang上的构造函数调用序列不同

时间:2017-03-10 21:09:10

标签: c++ g++ c++14 clang++

我有以下程序:

#include <iostream>

#define PRINT_LOCATION()\
    do { std::cout << __PRETTY_FUNCTION__ << "\n"; } while (false)

struct foo
{
    int val;

    foo()
        : val(1)
    {
        PRINT_LOCATION();
    }

    foo(const foo& other)
        : val(other.val * 2)
    {
        PRINT_LOCATION();
    }

    foo(foo&& other)
        : val(other.val * 2)
    {
        PRINT_LOCATION();
    }
};

int main()
{
    foo f{foo{foo{foo{}}}};

    std::cout << "value = " << f.val << "\n";

    if (f.val == 1)
        throw f;
}

编译和执行:

[mkc /tmp]$ g++ -Wall -Wextra -pedantic -std=c++14 -O0 -o a.out main.cpp
[mkc /tmp]$ ./a.out 
foo::foo()
value = 1
foo::foo(foo&&)
terminate called after throwing an instance of 'foo'
Aborted (core dumped)
[mkc /tmp]$ clang++ -Wall -Wextra -pedantic -std=c++14 -O0 -o a.out main.cpp
[mkc /tmp]$ ./a.out 
foo::foo()
foo::foo(foo &&)
foo::foo(foo &&)
value = 4
[mkc /tmp]$

我知道编译器可以删除一些构造函数调用,但是不允许在没有副作用的情况下执行它吗?看起来Clang在这里是正确的,它是GCC中的一个错误吗?

2 个答案:

答案 0 :(得分:3)

在C ++ 14中,两个编译器都是正确的。来自N4296中的[class.copy],我认为它接近于C ++ 14:

  

当满足某些条件时,允许实现省略类的复制/移动构造   即使为复制/移动操作选择的构造函数和/或对象的析构函数也是如此   有副作用。 [...]在下列情况下允许复制/移动操作的省略,称为复制省略(其中   可以合并以消除多份副本):
   - 在函数[...]中的return语句中    - 在 throw-expression (5.17)中,[...]
   - 何时复制/移动尚未绑定到引用(12.2)的临时类对象   对于具有相同cv-unqualified类型的类对象,可以省略复制/移动操作   将临时对象直接构造到省略的复制/移动的目标
   - 当异常处理程序的异常声明 [...]

此声明:

foo f{foo{foo{foo{}}}};

正好符合第三个标准,因此编译器允许,但不是必需,以避免复制/移动。因此,gcc和clang都是正确的。请注意,如果您不想要复制省略,则可以添加标记-fno-elide-constructors

在C ++ 17模式中,甚至没有移动到elide。 [dcl.init]中的初始化规则本身更改为:

  

如果目标类型是(可能是cv限定的)类类型:
   - 如果初始化表达式是prvalue,并且源类型的cv-nonqualified版本相同   class作为目标的类,初始化表达式用于初始化目标   宾语。 [示例: T x = T(T(T()));调用T默认构造函数来初始化x -end example]

答案 1 :(得分:1)

两者都不正确。这称为复制省略。正如@chris在下面指出的那样,它只是C ++ 17中的必需优化。更多细节可以在cppreference.com找到。 C ++ 17之前的相关部分是:

  

在以下情况下,允许编译器省略   复制 - 移动 - (自C ++ 11)类对象的构造函数,即使   复制/移动(自C ++ 11以来)构造函数和析构函数具有可观察性   副作用。

     

当无名的临时,未绑定任何引用时,将被移动   或者(从C ++ 11开始)复制到相同类型的对象中(忽略   顶级cv-qualification),省略了复制/移动(因为C ++ 11)。   构建临时构造时,它直接构造在   存储它将被移动或(自C ++ 11)复制到。   当无名临时是return语句的参数时,   复制省略的这种变体称为RVO,&#34;返回值   优化&#34;