移动构造函数与复制消除.哪一个叫? [英] Move Constructor vs Copy Elision. Which one gets called?

查看:122
本文介绍了移动构造函数与复制消除.哪一个叫?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在这里有两段代码向您展示.它们是两个类,每个类都提供一个Move构造函数和一个返回临时函数的函数.

I have two pieces of code here to show you. They are two classes and each one provides a Move Constructor and a function which returns a temporary.

  • 在第一种情况下,返回临时函数的函数调用 Move构造函数
  • 在第二种情况下,返回临时值的函数只是告诉编译器执行复制省略
  • In the first case, the function returning a temporary calls the Move Constructor
  • In the second case, the function returning a temporary just tells the compiler to perform a copy elision

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

I'm confused: in both cases I define a Move Constructor and a random member function returning a temporary. But the behavior changes, and my question is why.

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

已调用移动构造器

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] Move constructor called

0

1


执行了复制删除

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

6.28


我期望在第二个示例中调用Move构造函数. 毕竟,该函数的逻辑是相同的:返回一个临时值.


I was expecting a call to the Move Constructor in the second example. After all, the logic for the function is the same: return a temporary.

有人会解释我为什么没有发生吗?

Will someone explain me why that's not happening?

如何处理复制省略?

我希望我的代码具有最大的可移植性,如何确定编译器对这些优化的了解?

I want my code to be the most portable I can, how can I be sure of these kinds of optimizations by the compiler?

推荐答案

另一个SO答案的评论中, OP在这里澄清了他的要求:

In the comments of another SO answer, the OP clarifies what he is asking here:

我听说即使超过1个也可能发生复制省略 返回语句.我想知道什么时候禁止复制省略

I heard that copy elision CAN occur even when there are more than 1 return statements. I'd like to know when a copy elision is forbidden

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

And so I am attempting to address this issue here:

删除复制/移动操作(C ++标准称为 copy elision ):

Elision of copy/move operations (referred to as copy elision by the C++ standard) is permitted in the following circumstances:

  • 在具有类返回类型的函数中的return语句中,当 expression 是具有自动存储持续时间的非挥发性对象的名称时(函数参数或由处理程序的 exception-declaration 引入的变量,其类型与函数返回类型相同(忽略cv限定),可以通过将自动对象直接构造为函数的返回值.

  • In a return statement in a function with a class return type, when the expression is the name of anon-volatile object with automatic storage duration (other than a function parameter or a variable introduced by the exception-declaration of a handler) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value.

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

In a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object can be omitted by constructing the automatic object directly into the exception object.

当尚未绑定到引用的临时类对象将被复制/移动到相同类型的类对象(忽略cv限定)时,可以通过构造以下形式来省略复制/移动操作:临时对象直接进入省略的复制/移动的目标.

When a temporary class object that has not been bound to a reference would be copied/moved to a class object with the same type (ignoring cv-qualification), the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move.

当异常处理程序的 exception-declaration 声明与异常对象具有相同类型(cv限定除外)的对象时,可以通过将 exception-declaration 作为异常对象的别名,如果程序的含义将保持不变(除了由异常声明所声明的对象的构造函数和析构函数的执行之外).异常对象始终是左值,因此无法移动.

When the exception-declaration of an exception handler declares an object of the same type (except for cv-qualification) as the exception object, the copy operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. There cannot be a move from the exception object because it is always an lvalue.

在所有其他情况下,均禁止复制省略.

Copy elision is forbidden in all other circumstances.

函数中return语句的数量与复制省略的合法性无关.但是,即使编译器合法,出于任何原因,包括返回语句的数量,都可以执行复制省略.

The number of return statements in a function has no bearing whatsoever on the legality of copy elision. However a compiler is permitted to not perform copy elision, even though it is legal, for any reason at all, including the number of return statements.

C ++ 17更新

现在在某些地方必须强制执行复制省略.如果prvalue可以直接绑定到按值函数参数,按值返回类型或命名局部变量,则在C ++ 17中必须使用复制省略.这意味着即使检查副本或移动构造函数,编译器也不会打扰.法律C ++ 17:

There are now a few places where copy elision is mandatory. If a prvalue can be bound directly to a by-value function parameter, or a by-value return type, or to a named local variable, copy elision is mandatory in C++17. This means that the compiler shall not bother even checking for a copy or move constructor. 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{});
}

这篇关于移动构造函数与复制消除.哪一个叫?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆