对于以下代码段:
#include <utility>
#include <iostream>
#define C(name) (name ? name : "nullptr")
#define PP { std::cout << __PRETTY_FUNCTION__ << " : " << C(name) << '\n'; }
#define T { std::cout << __PRETTY_FUNCTION__ << " : " << C(name) << " -> " << C(rhs.name) << '\n'; }
struct A
{
const char * name = nullptr;
A(const char * name) : name{name} PP
A(A && rhs) : name{std::exchange(rhs.name, nullptr)} PP
A(const A & rhs) : name{rhs.name} PP
A & operator = (A && rhs) { T; std::swap(name, rhs.name); return *this; }
A & operator = (const A && rhs) { T; name = rhs.name; return *this; }
~A() PP
};
#include <random>
int main()
{
std::random_device d;
A a{"a"};
A b{"b"};
A c{"c"};
std::cout << "begin\n";
a = ((d() % 2) == 0) ? b : std::move(c);
std::cout << "end\n";
}
可能有以下两个输出:
A::A(const char*) : a
A::A(const char*) : b
A::A(const char*) : c
begin
A::A(A&&) : c
A& A::operator=(A&&) : a -> c
A::~A() : a
end
A::~A() : nullptr
A::~A() : b
A::~A() : c
和
A::A(const char*) : a
A::A(const char*) : b
A::A(const char*) : c
begin
A::A(const A&) : b
A& A::operator=(A&&) : a -> b
A::~A() : a
end
A::~A() : c
A::~A() : b
A::~A() : b
在上述情况下,编译器是否有可能(根据标准)避免在使用三元运算符进行复制/移动赋值期间使用临时值,并调度复制或移动赋值运算符来赋值右侧值(根据情况将b
或c
)直接移到左侧(a
)上?
答案 0 :(得分:1)
编译器是否可以避免在使用三元运算符进行复制/移动分配期间使用临时值
由于您编写代码的方式,这是一个有趣的问题。
在 general 中,是的。如果可观察到的结果与执行代码相同,则允许编译器重新排列或取消代码。这就是所谓的规则。
进一步允许编译器在其他情况下取消副本,即使观察到的行为发生了变化,例如RVO(收益优化)。
但是,在您的情况下,所有构造函数都有可观察到的行为,无法更改-它们向stdout发出字符!
因此,在这种特殊情况下,编译器别无选择,只能遵循原始代码的流程。
答案 1 :(得分:1)
如果我们查看标准草案[class.copy.elision]中有关复制/移动省略的部分,其中涵盖了即使有副作用也可以消除复制/移动的情况。我们看不到涉及您示例的任何情况:
满足某些条件时,允许实现省略类的复制/移动构造 对象,即使为复制/移动操作选择了构造函数和/或为对象选择了析构函数 有副作用。在这种情况下,实现将忽略的副本/移动的来源和目标 作为引用同一对象的两种不同方式的简单操作。如果选择第一个参数 构造函数是对对象类型的右值引用,当目标 会被摧毁;否则,销毁发生在两个对象的较晚时间 119如果没有进行复制/移动操作,则称为复制 在以下情况下允许省略(可以合并以消除多个副本):
- 在具有类返回类型的函数中的return语句中,当表达式为 非易失性自动对象(功能参数或由变量引入的变量除外) 与函数具有相同类型(忽略cv限定)的处理程序(13.3)的异常声明 返回类型,通过直接构造自动对象可以省略复制/移动操作 进入函数调用的返回对象
- 在throw表达式(7.6.17)中,当操作数是非易失性自动对象的名称时 (函数或catch子句参数除外),其范围不会超出 最里面的try块(如果有的话),从操作数到对象的复制/移动操作 通过将自动对象直接构造到异常中,可以省略异常对象(13.1) 对象
- 当异常处理程序的异常声明(第13条)声明相同对象时 类型(cv限定除外)作为异常对象(13.1),可以通过以下方式省略复制操作: 如果程序的意思是将异常声明视为异常对象的别名, 除了执行由构造函数声明的对象的构造函数和析构函数外,其他保持不变 异常声明。 [注意:由于异常对象始终是 左值。 —尾注]
在需要常量表达式(7.7)的上下文中对表达式求值的情况下,需要省略复制 并进行常量初始化(6.8.3.2)。 [注意:如果相同的表达式是 在另一种情况下进行评估。 —尾注]