简体   繁体   English

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

[英]Constructor call sequence different on GCC and clang

I have the following program: 我有以下程序:

#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;
}

Compilation and execution: 编译与执行:

[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]$

I know that the compiler is allowed to remove some constructor calls, but isn't it only allowed to do it when there are no side effects? 我知道编译器可以删除一些构造函数调用,但不是只有在没有副作用的情况下才允许这样做吗? It looks like Clang is correct here, is it a bug in GCC? 看起来Clang在这里是正确的,这是GCC中的错误吗?

In C++14, both compilers are correct. 在C ++ 14中,两个编译器都是正确的。 From [class.copy] in N4296, which I think is close to C++14: 来自N4296的[class.copy],我认为它接近C ++ 14:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. 当满足某些条件时,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。 [...] This elision of copy/move operations, called copy elision , is permitted in the following circumstances (which may be combined to eliminate multiple copies): [...]在以下情况下允许使用此复制/移动操作的省略,称为复制删除(可以合并以消除多个副本):
— in a return statement in a function [...] —在函数的return语句中[...]
— in a throw-expression (5.17), [...] —在throw-expression (5.17)中,[...]
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move 当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv-unqualtype类型的类对象时,可以通过将临时对象直接构造为以下形式来省略复制/移动操作:省略复制/移动的目标
— when the exception-declaration of an exception handler [...] —当异常处理程序的异常声明时[...]

This declaration: 此声明:

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

precisely meets that third criteria, so the compiler is allowed to, but isn't required to, elide that copy/move. 完全符合第三个条件,因此编译器可以 (但不是必须 )取消该复制/移动。 Hence, both gcc and clang are correct. 因此,gcc和clang都是正确的。 Note that if you do not want copy elision, you can add the flag -fno-elide-constructors . 请注意,如果不想复制省略号,则可以添加标志-fno-elide-constructors elide -fno-elide-constructors


In C++17 mode, there would not even be a move to elide. 在C ++ 17模式下,甚至不会采取任何行动。 The initialization rules themselves in [dcl.init] change to read: [dcl.init]中的初始化规则本身更改为:

If the destination type is a (possibly cv-qualified) class type: 如果目标类型是(可能是cv限定的)类类型:
— If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. —如果初始化程序表达式是prvalue,并且源类型的cv不合格版本与目标程序的类相同,则使用初始化程序表达式来初始化目标对象。 [ Example: T x = T(T(T())); [示例: T x = T(T(T())); calls the T default constructor to initialize x . 调用T默认构造函数初始化x —end example ] —末例]

Neither is incorrect. 两者都不对。 This is called copy elision. 这称为复制省略。 As @chris pointed out below, it is only a required optimization in C++17. 就像@chris在下面指出的那样,它只是C ++ 17中的必需优化。 More details can be found on cppreference.com . 可以在cppreference.com上找到更多详细信息。 The relevant section prior to C++17 is: C ++ 17之前的相关部分是:

Under the following circumstances, the compilers are permitted to omit the copy- and move- (since C++11)constructors of class objects even if copy/move (since C++11) constructor and the destructor have observable side-effects. 在以下情况下,即使复制/移动(自C ++ 11起)构造函数和析构函数具有明显的副作用,也允许编译器省略类对象的copy-和move-(自C ++ 11起)构造函数。

When a nameless temporary, not bound to any references, would be moved or (since C++11) copied into an object of the same type (ignoring top-level cv-qualification), the copy/move (since C++11) is omitted. 当将无名称的临时对象(不受任何引用约束)移动或(自C ++ 11起)复制到相同类型的对象中(忽略顶级cv限定)时,将进行复制/移动(自C ++ 11起) )省略。 When that temporary is constructed, it is constructed directly in the storage where it would otherwise be moved or (since C++11) copied to. 构造该临时文件后,将直接在存储中构建该临时文件,否则将其移动或复制到(自C ++ 11起)。 When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization". 当无名临时变量是return语句的参数时,复制省略的这种变体称为RVO,即“返回值优化”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 GCC和Clang对constexpr构造函数的不同行为 - GCC and Clang different behaviors on constexpr constructor "对于基类中的模板化构造函数,clang\/gcc 和 MSVC 之间的结果不同" - Different results between clang/gcc and MSVC for templated constructor in base class GCC 上的模糊构造函数重载,但不是 Clang - Ambiguous constructor overload on GCC, but not on Clang gcc和clang都忽略了下面代码段中对move构造函数的调用。 这个对吗? - gcc and clang both elide the call to the move constructor in the snippet below. Is this correct? GCC和Clang之间的行为不同 - Different behaviors between GCC and Clang 不同的clang和gcc行为与指针 - different clang and gcc behavior with pointers 序列点编译器警告仅在 gcc 而不是在 clang - Sequence point compiler warning only in gcc but not in clang 为什么gcc和clang分别为此程序产生不同的输出? (转换运算符与构造函数) - Why do gcc and clang each produce different output for this program? (conversion operator vs constructor) 使用gcc接受的“noexcept”构造函数的程序,被clang拒绝 - Program with “noexcept” constructor accepted by gcc, rejected by clang constexpr(但不是真的)构造函数在gcc中编译,但不在clang中编译 - constexpr (but not really) constructor compiles in gcc but not in clang
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM