[英]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 areturn
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 theT
default constructor to initializex
.调用
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.