移动构造函数与复制Elision。哪一个被调用?

时间:2016-02-19 13:38:49

标签: move move-semantics deep-copy move-constructor copy-elision

我这里有两段代码给你看。它们是两个类,每个类都提供一个Move Constructor和一个返回临时函数的函数。

  • 在第一种情况下,返回临时函数会调用移动构造函数
  • 在第二种情况下,返回临时函数只会告诉编译器执行复制省略

我很困惑:在这两种情况下,我都定义了一个Move Constructor和一个返回临时的随机成员函数。但是行为发生了变化,我的问题是为什么

请注意,在以下示例中,运算符<<重载是为了打印列表(在第一种情况下)和双数据成员(在第二种情况下)。

移动构造词得到了

template<typename T>
class GList
{
public:
    GList() : il{ nullptr } {}

    GList(const T& val) : il{ new Link<T>{ val,nullptr } }  {}

    GList(const GList<T>& copy) {}

    GList(GList<T>&& move)
    {
        std::cout << "[List] Move constructor called" << std::endl;

        // ... code ...
    }

    // HERE IS THE FUNCTION WHICH RETURNS A TEMPORARY!
    GList<T> Reverse()
    {
        GList<T> result;

        if (result.il == nullptr)
            return *this;

        ...
        ...
        ...

        return result;
    }
};

int main()
{

   GList<int> mylist(1);

   mylist.push_head(0);

   cout << mylist.Reverse();

   return 0;
}

输出结果为:

  

[List]移动名为

的构造函数      

0

     

1

执行COPY ELISION

class Notemplate
{
   double d;
public:
   Notemplate(double val)
   {
      d = val;
   }

   Notemplate(Notemplate&& move)
   {
       cout << "Move Constructor" << endl;
   }

   Notemplate(const Notemplate& copy)
   {
       cout << "Copy" << endl;
   }

   Notemplate Redouble()
   {
       Notemplate example{ d*2 };
       return example;
   }
};

int main()
{
   Notemplate my{3.14};

   cout << my.Redouble();

   return 0;
}

输出结果为:

  

6.28

我希望在第二个例子中调用Move Constructor。 毕竟,函数的逻辑是相同的:返回一个临时的。

有人会解释为什么没有发生这种情况吗?

我如何处理复制品?

我希望我的代码是最便携的,我怎样才能确定编译器的这些优化?

2 个答案:

答案 0 :(得分:9)

another SO answer的评论中,OP澄清了他在这里的要求:

  

我听说即使超过1,也会发生复制   退货声明。我想知道禁止复制的时间

所以我试图在这里解决这个问题:

在以下情况下,允许删除复制/移动操作(C ++标准称为复制省略):

  • 在具有类返回类型的函数的return语句中,当表达式是具有自动存储持续时间的非易失性对象的名称时(函数除外)参数或由处理程序的 exception-declaration 引入的变量)与函数返回类型相同的类型(忽略cv-qualification),通过构造自动对象可以省略复制/移动操作直接进入函数的返回值。

  • throw-expression 中,当操作数是非易失性自动对象的名称(函数或catch子句参数除外),其范围不超出在最里面的 try-block 的末尾(如果有的话),通过将自动对象直接构造到异常对象中,可以省略从操作数到异常对象的复制/移动操作。

  • 当一个未绑定到引用的临时类对象被复制/移动到具有相同类型的类对象(忽略cv-qualification)时,可以通过构造它来省略复制/移动操作临时对象直接进入省略的复制/移动目标。

  • 当异常处理程序的 exception-declaration 声明一个相同类型的对象(cv-qualification除外)作为异常对象时,可以通过处理复制操作来省略复制操作如果除了为exception-declaration声明的对象执行构造函数和析构函数之外,程序的含义将保持不变,则将 exception-declaration 作为异常对象的别名。不能从异常对象移动,因为它始终是左值。

在所有其他情况下禁止复制省略。

函数中的返回语句数对复制省略的合法性没有任何影响。但是,允许编译器执行复制省略,即使它是合法的,出于任何原因,包括返回语句的数量。

C ++ 17更新

现在有一些地方需要复制省略。如果prvalue可以直接绑定到by-value函数参数,或者按值返回类型,或者命名为局部变量,则在C ++ 17中必须使用copy elision。这意味着编译器甚至不会打扰检查副本或移动构造函数。 Legal C ++ 17:

struct X
{
    X() = default;
    X(const X&) = delete;
    X& operator=(const X&) = delete;
};

X
foo(X)
{
    return X{};
}

int
main()
{
    X x = foo(X{});
}

答案 1 :(得分:0)

复制省略是一种优化,现在,每个现代编译器都提供。

在C ++中返回大型类对象时,这种技术适用于...... ,但不适用于所有情况!

在第一个示例中,编译器执行Move Constructor,因为我们在函数中有多个return语句