这段代码可以编译,但是我只是开始学习C++11,无法理解背后发生了什么。
void func(int&& var)
{
int var2(var);
}
void main()
{
int var1 = 22;
func(move(var1));
}
我的猜测是:move(var1)返回var1的右值(可能是其数据),而func函数使用var1的右值初始化var2。但是为什么在调用func之后,var1仍然有效呢?它的临时值不应该已重新分配给var2而无效了吗?
int
。对于一个 int
来说,复制和移动的速度是一样快的。因此,将移动实现为复制是完全合理的。::std::move
并不会导致任何东西被移动。它只是一种将 lvalue 转换为 rvalue 的好方法,以便可以移动某些东西。在你的情况下,var
有一个名称,所以它是一个 lvalue。你在 func
中初始化的 var2
实际上是一个复制,而不是一个移动。如果你写成 int var2(move(var));
,那么它就是一个移动操作,此时 main
中的 var1
可能会失效。::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
。对于int类型,没有什么需要移动的,因此会创建一个副本。对于具有移动构造函数或移动赋值运算符并且具有特定目的以移动底层资源的类型,将发生更改,否则将进行复制。
std::move会将左值转换为右值,以便可以将某些资源(堆上的对象或文件句柄)移动到其他对象中,但实际的移动是在移动构造函数或移动赋值运算符中发生的。
例如,考虑标准库中的向量,它具有移动复制构造函数和移动赋值运算符,可以执行一些显式工作以跨越移动资源。
话虽如此,我认为在函数func内部移动类型的方式不正确。int&& var是对rvalue的引用(通过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.
}
var1
是lvalue。应用std::move(var1)
会给你一个引用同一对象的rvalue。然后,您将此rvalue绑定到名为var
的int&&
中。var
也是lvalue。这是因为任何命名变量的表达式都是lvalue(即使其类型是rvalue引用)。然后,您使用var
的值初始化var2
。var1
的值复制到var2
。根本没有移动任何东西。事实上,您甚至不能从像int
这样的基本类型中移动。尝试从临时int
初始化或赋值只会复制它的值,而不会移动任何内容。T
,您的代码也不会移动任何内容。那是因为您唯一的非引用初始化是int var2(var);
,但在这里var
是lvalue。这意味着将使用复制构造函数。var1
移动到var2
的最简单方法是这样做:void func(T var)
{
T var2(move(var));
}
void main()
{
T var1(22);
func(move(var1));
}
这将从var1
移动到创建var
,然后再从var
移动到创建var2
。
你可以用几乎相同的方式做到这一点,只需将var
更改回右值引用即可,但我不建议这样做。你需要记录传递的右值将被内部移动使其无效。
move(var1) 返回 var1 的一个 r-value(可能是它的数据)
不,move(var1)
返回一个 rvalue 引用,指向 var1
。
func 函数正在使用 var1 的 r-value 初始化 var2
func
将 var1
复制到 var2
。如果 var1
是具有移动构造函数的类型,则将调用移动构造函数来初始化 var2
。但它不是这样的。
但为什么在 func 调用后 var1 仍然有效?
因为复制一个 int
不会使其无效。
它不应该有一个无效值吗?
int
没有特殊的“无效值”。使用未初始化的对象具有未定义的行为,但此对象并未未初始化。
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
已经被移动,并且不再包含任何数据,但是根据标准它仍处于未指定的状态,并且仍可重用。
int
类型转移只是复制。 - Tony The Lionstd::move
函数必须保持参数有效。这意味着它并不需要实际移动所有类型。像int
这样的基本类型将不会被移动,因为那样会使它们无效。 - Some programmer dudeint var2(var);
无论如何都不会将var移动到var2中,而是会复制(将调用复制构造函数)。尝试“移动”的正确方法是int 'var2(std :: move(var));' - Abhijit-Kint main
,并且去掉using namespace std;
,这样会使代码更清晰 :) - David Rodríguez - dribeas