三元运算符中的条件移动或复制分配

时间:2018-10-27 06:48:29

标签: c++ language-lawyer ternary-operator move-semantics value-categories

对于以下代码段:

#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

在上述情况下,编译器是否有可能(根据标准)避免在使用三元运算符进行复制/移动赋值期间使用临时值,并调度复制或移动赋值运算符来赋值右侧值(根据情况将bc)直接移到左侧(a)上?

2 个答案:

答案 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)。 [注意:如果相同的表达式是   在另一种情况下进行评估。 —尾注]