4

这段代码可以编译,但我刚刚开始学习 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 吗?

4

5 回答 5

6

这里有几个问题。

一个是您正在使用int. 而对于int副本来说,速度与移动一样快。因此,将移动作为副本实施是完全合理的。

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

第二个是您编写的代码实际上并没有移动任何东西。简单地使用::std::move不会导致某些东西被移动。这只是将左值转换为右值以便移动某些东西的好方法。在你的情况下,var有一个名字,所以它是一个左值。var2您对in的初始化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. 没有明确使用moverequired。

于 2013-04-02T12:11:00.607 回答
3

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

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

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

话虽如此,我相信类型在函数内部移动的func方式不是尝试移动的正确方式。int&& var是对右值的引用(用 std::move 强制转换)但是var (按名称)就像一个左值,int var2(var);无论如何都不会将 var 移动到 var2 并且它将是 COPY。尝试移动的正确方法是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. 
}
于 2013-04-02T12:08:55.440 回答
2

表达式var1是一个左值。Applyingstd::move(var1)为您提供了一个指向同一对象的右值。然后将此右值绑定到被int&&调用的var.

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

因此,您所做的只是将值从var1to复制到var2. 什么都没有被移动。事实上,你甚至不能从像int. 尝试从临时初始化或分配int只会复制其值而不移动任何内容。

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

var1从to移动的最简单方法var2是这样做:

void func(T var)
{
    T var2(move(var));
}

void main()
{

    T var1(22);
    func(move(var1));
}

这将从var1创建移动var,然后从var创建移动var2

可以以几乎相同的方式执行此操作,除了更改var回右值引用,但我不推荐它。您必须记录所传递的右值将因内部移动而失效。

于 2013-04-02T12:12:59.097 回答
1

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

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

并且 func 函数正在使用 var1 的 r 值初始化 var2

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

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

因为复制 anint不会使其无效。

它不应该有一个无效的值吗?

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

于 2013-04-02T12:17:54.497 回答
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确实已被移出,并且不再包含任何数据,但是根据标准它仅处于未指定状态并且仍然可以重用。

现场示例

于 2013-04-02T12:18:14.267 回答