C ++ 11移动和重新分配

时间:2013-04-02 12:04:48

标签: c++ c++11

这段代码已编译但我刚刚开始学习C ++ 11,我无法理解幕后发生的事情。

void func(int&& var)
{
    int var2(var);
}

void main()
{

    int var1 = 22;
    func(move(var1));
}

我的猜测:move(var1)返回var1的r值(可能是它的数据),func函数用var1的r值初始化var2。但是为什么在func调用之后var1仍然有效?它的值是否应该无效,因为其临时值已重新分配给var2?

5 个答案:

答案 0 :(得分:6)

这里有几个问题。

一个是你正在使用int。对于int而言,副本与移动一样快。因此,将此举作为副本实施是完全合理的。

没有要求移动构造(或赋值)改变被移动物体的值。代码中的逻辑错误是继续将其视为具有有用的值,但不要求该值变得无用。

第二个是你写的代码实际上并没有移动任何东西。简单地使用::std::move不会导致移动内容。这是将左值变为右值的一种很好的方法,可以移动 。在您的情况下,var有一个名称,所以它是一个左值。您在var2中初始化func实际上是一个副本,而不是一个移动。如果您已将其写为int var2(move(var));,那么这将是一个移动,var1中的main可能会在此时失效。

重申一下,::std::move函数只是向那些读取代码的人发出信号,表明可能会发生移动,并且之后的变量值无法计算在内。它实际上并没有移动任何东西。

以下是代码的标记版本:

// func requires a temporary argument, something that can be moved from
void func(int&& var)
{
    int var2(var); // Doesn't actually call int(int &&), calls int(int &)
    // int var2(move(var));  // Would actually call int(int &&) and a move would
                             // happen then at this point and potentially alter the
                             // value of what var is referencing (which is var1
                             // in this code).
}

void main()
{
    int var1 = 22;
    func(move(var1)); // Tell people that var1's value may be unspecified after
                      // Also, turn var1 into a temporary to satisfy func's
                      // signature.
}

由于您编写的代码不会导致任何移动,因此这个版本肯定会在某处移动:

#include <vector>
#include <utility>

using ::std;

// Still require an rvalue (aka a temporary) argument.
void func(vector<int>&& var)
{
   // But, of course, inside the function var has a name, and is thus an lvalue.
   // So use ::std::move again to turn it into an rvalue.
   vector<int> var2(move(var));
   // Poof, now whetever var was referencing no longer has the value it used to.
   // Whatever value it had has been moved into var2.
}

int main()
{
   vector<int> var1 = { 32, 23, 66, 12 };
   func(move(var1)); // After this call, var1's value may no longer be useful.
   // And, in fact, with this code, it will likely end up being an empty
   // vector<int>.
}

当然,这种写作方式很愚蠢。有时会有理由指出参数是临时的。通常,如果你有一个版本的函数需要引用,另一个版本需要一个右值引用。但通常你不这样做。所以,这是编写此代码的惯用方法:

#include <vector>
#include <utility>

using ::std;

// You just want a local variable that you can mess with without altering the
// value in the caller. If the caller wants to move, that should be the caller's
// choice, not yours.
void func(vector<int> var)
{
   // You could create var2 here and move var into it, but since var is a
   // local variable already, why bother?
}

int main()
{
   vector<int> var1 = { 32, 23, 66, 12 };
   func(move(var1));  // The move now does actually happen right here when
                      // constructing the argument to func and var1's value
                      // will change.
}

当然,在该代码中为var1命名是有点愚蠢的。真的,它应该这样写:

   func(vector<int>{ {32, 23, 66, 12} });

然后你就是在那里构建一个临时向量并将其传递给func。没有明确使用move

答案 1 :(得分:3)

没有什么可以从int移动,因此创建了一个副本。对于具有移动构造函数或移动赋值的类型,操作符具有移动底层资源的特定目的,那么其他方面则存在更改它是副本。

std :: move会将左值转换为右值,以便某些资源(堆或文件句柄上的对象)可以移出到其他对象,但实际移动发生在移动构造函数或移动赋值运算符中。

例如,考虑标准库中的向量,它具有移动复制构造函数和移动赋值运算符,可以执行一些显式工作来移动资源。

说完这个后,我相信在函数func中移动类型的方式不是尝试移动的正确方法。 int&& var是对rvalue的引用(使用std :: move进行转换)但是var(按名称)就像左值而int var2(var);无论如何都不会将var移动到var2并且它将是复制。尝试移动的正确方法是int var2(std::move(var));我的意思是如果一个类型有移动构造函数,它可以移动你必须使用的资源。

void func(int&& var) //2. Var is a reference to rvalue however the by name the var is a lvalue and if passed as it would invoke copy constructor.
{
    int var2(std::move(var)); // 3. hence to invoke move constructor if it exists the correct attempt would be to case it to rvalue again as move constructors take rvalue. If the move constructor does not exists then copy is called. 
}

void main()
{

    int var1 = 22;
    func(move(var1)); //1. Cast the type to rvalue so that it can be passed to a move copy contructor or move assigment operator. 
}

答案 2 :(得分:2)

表达式var1是左值。应用std::move(var1)会为您提供引用同一对象的右值。然后,将此左值绑定到名为int&&的{​​{1}}。

表达式var也是左值。这是因为任何作为命名变量的表达式都是左值(即使它的类型是右值引用)。然后,您使用var的值初始化var2

因此,您所做的就是将值从var复制到var1。什么都没有动过。事实上,你甚至无法从像var2这样的基本类型移动。尝试初始化或从临时int分配只会复制其值而不会移动任何内容。

即使您使用的是具有移动构造函数和赋值运算符的类型int,您的代码也不会移动任何内容。这是因为您执行的唯一非参考初始化是行T,但此处int var2(var);是左值。这意味着将使用复制构造函数。

var迁移到var1的最简单方法是执行此操作:

var2

这将从void func(T var) { T var2(move(var)); } void main() { T var1(22); func(move(var1)); } 移至创建var1,然后它将从var移至创建var

可以以几乎相同的方式执行此操作,除了将var2更改回rvalue引用,但我不建议这样做。您必须记录传递的右值将被内部移动无效。

答案 3 :(得分:1)

  

move(var1)返回var值的r值(可能是其数据)

不,move(var1)返回右值引用,引用var1

  

并且func函数使用var-1的r值初始化var2

funcvar1复制到var2。如果var1是具有移动构造函数的类型,则将调用移动构造函数来初始化var2。但事实并非如此。

  

但是为什么在func调用之后var1仍然有效?

因为复制int并不会使其无效。

  

它不应该有无效值吗?

int没有特殊的“无效价值”。使用未初始化的对象具有未定义的行为,但此对象未初始化。

答案 4 :(得分:1)

正如其他人所说的那样,你的例子使用的是一种原始类型,它本质上会调用一个副本,因为这里的移动根本就不会发生。但是,我创建了一个使用std::string可以进行移动的示例。它的内脏可以从下面移出,你可以在这个例子中看到发生的事情。

#include<iostream>
#include<string>
#include<utility>

void func(std::string&& var)
{
    // var is an lvalue, so call std::move on it to make it an rvalue to invoke move ctor
    std::string var2(std::move(var));
    std::cout << "var2: " << var2 << std::endl;
}

int main()
{

    std::string var1 = "Tony";
    std::cout << "before call: " << var1 << std::endl;
    func(std::move(var1));
    std::cout << "after call: "  << var1 << std::endl;
}

输出:

before call: Tony
var2: Tony
after call: 

您可以看到var1确实已被移除,并且它不再包含任何数据,但它仅符合标准的未指定状态,并且仍可重复使用。

Live Example