我有以下程序:
#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中的一个错误吗?
答案 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;