【问题标题】:Is value category of function returning by value is always xvalue?按值返回的函数的值类别总是xvalue吗?
【发布时间】:2016-11-07 08:28:39
【问题描述】:

根据我的理解,下面的代码应该调用 Test 类的移动构造函数,因为这个函数是按值返回的,这意味着表达式 GetTestObj() 应该是右值并且 xvalues 被隐式移动但是为什么这段代码调用复制构造函数?

class Test
{
  public:
         Test()
         {
         }
         Test(const Test& arg)
         {
            std::cout<<"Copy Constructor Called..."<<std::endl;
         }
         Test(Test&& arg)
         {
            std::cout<<"Move Constructor Called..."<<std::endl;
         }
};

Test  GetMyTestObj()
{
      Test *ptr = new Test();
      return *ptr;
}
Test dummy = GetMyTestObj(); //Copy Constructor Called...

【问题讨论】:

  • 这在很大程度上取决于Test 类定义。您是否尝试过删除复制构造函数?你有一个移动构造函数?而且您知道您显示的代码中有内存泄漏吗?为什么不在函数中简单地创建一个简单的Test 对象实例(例如Test test;)并返回它?

标签: c++ c++11 c++14 move xvalue


【解决方案1】:

在您的代码中,实际上有一份从*ptr 复制到返回值,还有一份从GetMyTestObj() 移动到dummy。但是,编译器elides 移动了,所以您不会看到它被跟踪。如果您将 -fno-elide-constructors 传递给 GCC 或 Clang,那么您应该会看到副本和移动 (demo)。

如果要构造返回值来移动,需要使用std::move

Test GetMyTestObj()
{
      Test *ptr = new Test();
      return std::move(*ptr);
}

但是,在这种情况下确实不需要动态分配;它效率低下,并且您的实现会泄漏内存。你应该只使用一个自动变量:

Test GetMyTestObj()
{
    Test test;
    //I assume you're doing something else here
    return test;
}

使用上面的代码,编译器实际上可以省略这两种结构。

如果你在那个函数中什么都不做,你应该直接构造dummy

Test dummy{};

【讨论】:

    【解决方案2】:

    值没有被复制到这里(由于复制省略,没有调用构造函数):

    Test dummy = GetMyTestObj();
    

    但在这里:

    return *ptr;
    

    因为函数必须从左值引用生成右值对象。基本上,这种情况下的GetMyTestObj()函数相当于:

    Test *ptr = new Test();
    Test returnValue(*ptr);
    return returnValue;
    

    【讨论】:

      【解决方案3】:

      这意味着表达式GetTestObj() 应该是右值并且xvalues 被隐式移动

      首先,直接回答您的问题,GetTestObj()prvalue

      • 函数调用或非引用返回类型的重载运算符表达式,例如str.substr(1, 2)str1 + str2it++

      重点是return *ptr;需要复制/移动操作; ptr 是一个命名变量,它是一个lvalue*ptr 也是左值,不能移动。

      您可以显式使用std::move 使其成为可移动的xvalue:

      return std::move(*ptr);
      

      【讨论】:

      • 但是表达式 GetMyTestObj() 的值类别是什么
      • @Kapil 这是一个prvalue;但在这里没关系。复制发生在 return 语句中。
      • std::move 在这里无关紧要,因为它隐式应用于返回值,除非发生复制省略。这在标准中有所描述。所以,基本上,在 return 语句中添加std::move 只能防止优化
      • @AndreiR。这种情况不符合将返回视为右值的规则,因此std::move 是必要的。想象一下,如果ptr 是某个全局状态;如果仅通过按值返回指针来移动指针,您会感到非常惊讶。
      • @AndreiR。不是无关紧要,是用来解释为什么这里需要copy ctor,即为什么*ptr不能移动。
      猜你喜欢
      • 1970-01-01
      • 2012-11-03
      • 1970-01-01
      • 1970-01-01
      • 2019-12-03
      • 1970-01-01
      • 2018-06-08
      • 2016-09-26
      • 1970-01-01
      相关资源
      最近更新 更多