对象传递给STD ::移动,但不会移动

Object passed to std::move but not moved from?

本文关键字:移动 STD 对象      更新时间:2023-10-16

我正在审查一些这样的代码,其中 A是一种可移动类型:

// Returns true exactly when ownership of a is taken
bool MaybeConsume(A&& a) {
  if (some condition) {
    Consume(std::move(a));  // ???
    return true;
  }
  return false;
}
// ... elsewhere ...
A a;
if (!MaybeConsume(std::move(a))) {
  a.DoSomething();  // !!!
}

我们的静态分析工具抱怨被移动后使用a(在!!!(。IIUC std::move只是static_cast,直到调用移动构造函数或分配运算符(大概在Consume中(之前,对象a实际上不会被弄脏。假设MaybeConsume在评论中满足合同,

  1. 这有效吗?
  2. 是ub?
  3. std::move??? a no-op?

(可能可以重构这个特定的实例以避免微妙,但我仍然想寻求自己的理解(。

这是您静态分析工具的虚假警告。

  1. 这有效吗?

是的,MaybeConsume正在做评论所说的话。当some condition为True时,它仅具有其参数的所有权(假设Consume实际上确实从其参数中移动构造/分配(。

std::move确实只是一个花哨的static_cast<T&&>,因此MaybeConsume(std::move(a))不是转移所有权,您只是将引用绑定到MaybeConsume的参数。

  1. 是ub?

否,如果MaybeConsume表示它已承担其参数所有权,则您不使用a

  1. std::move ??? a no-op?

好吧,这是一个无关,因为它只是static_cast,但是如果您打算问它是否不必要,那么,不是,不是。在MaybeConsume的主体中,a是一个LVALUE,因为它具有名称。如果Consume的签名是void Consume(A&&),则没有std::move

就不会编译代码

从您显示的示例用法中,看来您不应该用prvalue参数调用 MaybeConsume,因为如果函数返回false,呼叫者大概应该以其他方式使用该参数。如果是这样,那么您应该将其签名更改为bool MaybeConsume(A&)。这可能会使您的静态分析工具感到高兴,因为这允许您编写if (!MaybeConsume(a))

要了解为什么静态分析工具发出警告,需要以静态分析仪的方式思考。当看到一块像以下的代码时:

A a;
fun(std::move(a);
a.method();

尚不清楚fun((呼叫内部可能会发生什么。在A上成功执行方法((取决于某些先决条件的满足,这可能(或不再(在fun of Fun((之后不再存在。虽然程序员很可能知道可以安全地调用方法((,但分析仪不会发出警告。

以下只是我自己的看法。假设A的所有权完全由Fun((完全占据。为了防止混乱,最好要执行借来返回的风格,就好像一个朋友从您那里借了一本书一样,您(不能(在返回之前(无法(使用该书。因此,永远不要冒险自己会意外援引那时应该"死"的对象。

请参阅以下演示代码:

#include <iostream>
#include <utility>
#include <tuple>
#include<cassert>
struct A {
public:
    int *p; 
public:
    A() {
        p = new int();
        assert(p != nullptr);
        std::cout << p << std::endl;
        std::cout << "default constrctor is called" << std::endl;
    }
    A(const A&) = delete;
    A& operator=(const A&) = delete;
    A(A&& _a): p(_a.p) {
        _a.p = nullptr;
        std::cout << p << std::endl;
        std::cout << "move constructor is called" << std::endl;;
    }
    A& operator=(A&& _a) {
        std::cout << "move assignment is called"<<std::endl;;
        p = std::move(_a.p);
        return *this;
    }
    void DoSomthing(){
        std::cout << "do somthing is called" << std::endl;
        *p = 100;
        std::cout << "value of p is changed"<<std::endl;
    }
};
std::tuple<A&&, bool>  MaybeConsume(A&& a) {
    if (1==2) {//try 1==1 alternatively
        delete a.p;
        a.p = nullptr;//consume
        std::cout << "content consumed" << std::endl;
        return std::make_tuple(Consume(std::move(a)), true);
    }
    else {
        return std::make_tuple(std::move(a), false);
    }
}
int main()
{
    A a;
    std::tuple<A&&, bool> t = MaybeConsume(std::move(a));
    if (!(std::get<bool> (t))) {
        A a1 = std::move(std::get<A&&>(t));
        a1.DoSomthing();
    }
    return 0;
}