std :: move有相反的操作吗?

6

std::move可以用来显式地允许移动语义,当移动不能隐式地允许时(例如通常在从函数返回本地对象时)。

现在,我想知道(特别是在本地返回和隐式移动的情况下),是否有与std::move相反的东西,它将阻止移动对象(但仍然允许复制)。

这有意义吗?


2
我确实有一个问题:你为什么需要这个? 可能甚至没有本地对象需要返回,它可以通过(N)RVO创建 - 理论上的 return std::copy_only(v); 会妨碍编译器执行该操作。 - Xeo
删除移动构造/赋值。仍无法防止RVO。 - sehe
2
std::unmove。可能也可以是std::demove。或者是antimove或dontmove?movewithoutmoving?movebackintime,moveelephanttoblockthepath?不是这样的吗? - Markus Meskanen
@Xeo - 我不需要它 :-) 只是好奇。 - Martin Ba
3
记录一下:我不认为这是链接问题的重复。(只需实际阅读问题及其答案即可了解原因)。 - Martin Ba
@Xeo C++禁止获取临时变量的地址。因此,任何需要传递指向临时值的指针的API都可以从getaddrinfo(ip, port, &static_cast<const addrinfo&>(addrinfo{ .ai_family = AF_INET }), &host);中受益。 - OwnageIsMagic
3个回答

4

std::move 将一个左值转换为一个右值,它本质上是通过 static_cast 实现的。我能想到与之相反的操作是下面这两种类型转换:

static_cast<T &>(/*rvalue-expression*/)
static_cast<const T&>(/*rvalue-expression*/)

下面是一个例子:
#include <iostream>

void f(const int &)
{ std::cout << "const-lval-ref" << std::endl; }

void f(int &&)
{ std::cout << "rval-ref" << std::endl; }

int main()
{
  f(static_cast<const int &>(3));
  return 0;
}

将 prvalue 值为 3 转换为 const int &,可确保选择 f 的 lvalue-overload。在大多数情况下,通过将值赋给变量,自动进行 rvalue-to-lvalue 修改:
int a = 3;

当您在此行后使用a时,它将成为一个左值。即使a被声明为右值引用,这也是正确的:
int &&a = 3;

在这里,a也成为了一个lvalue(基本上是因为它“有一个名字”)。
我唯一能想象到显式转换有任何相关影响的情况是我上面的第一个例子。在那里,当处理像3或通过复制从函数调用返回的临时值时,唯一合法的转换是到const引用(非const引用不允许绑定到prvalue)。

将静态转换为const引用static_cast<const&>是否真的可以防止函数返回时的隐式移动? - Martin Ba
1
不,函数返回的类型最终由函数声明中的返回类型确定。如果您在返回之前将对象强制转换,它最终将再次被转换为官方返回类型。如果该返回类型不是引用,您将得到一个prvalue;如果是rvalue-reference,则会得到xvalue。(但这等同于std::move的行为:如果函数返回类型被定义为左值引用,则在返回之前执行std::move将不启用移动语义。) - jogojapan
所以,如果出于任何扭曲的原因,有人想要阻止函数返回时的隐式移动,那么就必须创建第二个本地变量来保存返回值,然后返回该变量,对吧?(T local1; ... T local2 = local1; /*<- 复制*/ ... return local2; /*<- 可能会移动*/) - Martin Ba
@MartinBa 但是,正如你在评论中所说的那样,return local2; 可能仍然会触发移动操作,对吗?这种方式如何防止移动操作发生呢? - jogojapan
local2阻止了原始对象local1的移动。我的意思是,如果我处于不希望本地对象被移动的情况下,我可以将"其值"复制到另一个本地对象中并返回该对象。这样原始的本地对象就不会被返回,因此也不会被移动。(是的,我真的无法想象何时会想要这样做。) - Martin Ba
1
@MartinBa 啊,是的,没错。当然,在return时,local1将会超出作用域,而在此之前,local2尚未移动,所以确实......没有什么用处。但是再次强调,就像std::move不能覆盖已声明的返回类型一样,我的建议转换也不能这样做。但我相信std::move的主要目的是指导编译器选择正确的函数重载(即通常应用于函数参数,而不是在返回语句中)。在这个函数中,建议的转换是一个很好的"反移动"候选项。 - jogojapan

1
template<class T>
T& unmove(T&& t)
{
    return t;
}

这将改变参数表达式的值类别为lvalue,无论它最初是什么。

void f(const int&); // copy version
void f(int&&); // move version

int main()
{
    int i = ...;

    f(42); // calls move version
    f(move(i)); // calls move version
    f(i); // calls copy version

    f(unmove(42)); // calls copy version
    f(unmove(move(i))); // calls copy version
    f(unmove(i)); // calls copy version
}

当然,这并没有解决问题中特别强调的隐式返回移动的情况。 - Martin Ba
是的,return unmove(local_variable); - Martin Ba
@MartinBa:是的,在那里使用“unmove”也可以抑制返回值的RVO和移动构造函数 - 强制执行复制构造函数。 - Andrew Tomazos
这与@jogojapan在评论中提到的内容相矛盾。 - Martin Ba
阅读标准(试图理解其含义)甚至更加低效;-) - 而且:“函数返回的类型最终由函数声明中的返回类型确定。如果在返回之前将对象强制转换,它最终将再次转换为官方返回类型。如果该返回类型不是引用,则会得到一个prvalue;如果它是rvalue-reference,则会得到一个xvalue”*对我来说,这意味着return unmove(local);不起作用。这就是我这样阅读的。 - Martin Ba
显示剩余5条评论

0
防止一个对象被移动的解决方案是将对象的移动构造函数设为私有,这样对象就不能被移动但可以被复制。
示例程序:
enter code here

#include <iostream>

class A {
public:
    std::string s;
    A() : s("test") {}
    A(const A& o) : s(o.s) { std::cout << "move failed!\n";}
    A(A&& o) : s(std::move(o.s)) { std::cout << "move was succesfull!\n"; }
};

int main(int argc, const char * argv[])
{
    A firsObject;
    A secondObject = std::move(firsObject);
    return 0;
}

禁用移动的示例:

#include <iostream>

class A {
public:
    std::string s;
    A() : s("test") {}
    A(const A& o) : s(o.s) { std::cout << "move failed!\n";}

private:
    A(A&& o) : s(std::move(o.s)) { std::cout << "move was succesfull!\n"; }
};

int main(int argc, const char * argv[])
{
    A firsObject;
    A secondObject = std::move(firsObject);
    return 0;
}

那么为什么还需要构造函数呢? - R. Martinho Fernandes
@R. Martinho Fernandes,你是对的,移动构造函数可以被省略。在上面的情况中,我考虑的是当你需要它时,这样你就可以声明一个友元类/方法,如果必要的话仍然可以使用移动构造函数。 - Pascalau Razvan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接