Объект передан в std :: move, но не перемещен?

Я просматриваю некоторый код, подобный этому, где 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, и объект a фактически не будет портиться до тех пор, пока не будет вызван конструктор перемещения или оператор присваивания (предположительно в Consume). Предполагая, что MaybeConsume удовлетворяет контракту в комментарии,

  1. Это работает?
  2. Это UB?
  3. Является std::move at ??? нет-op?

(Вероятно, этот конкретный экземпляр можно реорганизовать, чтобы избежать тонкости, но я все равно хотел бы попросить мое собственное понимание).

Ответ 1

Это ложное предупреждение от вашего инструмента статического анализа.

  1. Это работает?

Да, MaybeConsume делает то, что говорится в комментарии. Он принимает на себя только свой аргумент, когда some condition истинно (предполагая, что Consume действительно перемещает конструкцию/назначает из своего аргумента).

std::move действительно просто причудливый static_cast<T&&> поэтому MaybeConsume(std::move(a)) не передает права собственности, вы просто привязываете ссылку на параметр MaybeConsume.

  1. Это UB?

Нет, вы не используете a если MaybeConsume указывает, что приобрела право собственности своего аргумента.

  1. Является std::move at??? нет-op?

Ну, это не-op, потому что это просто static_cast, но если вы хотите спросить, не нужно ли это, то нет, это не так. Внутри тела MaybeConsume, a является lvalue, потому что у него есть имя. Если подпись Consume является void Consume(A&&), тогда код не будет компилироваться без этого std::move.


Из примера использования, который вы показали, кажется, вы не должны вызывать MaybeConsume с аргументом prvalue, так как вызывающий должен использовать аргумент каким-либо другим способом, если функция возвращает false. Если это так, то вы должны изменить свою подпись на bool MaybeConsume(A&). Это, вероятно, сделает ваш инструмент статического анализа счастливым, потому что это позволит вам написать if (!MaybeConsume(a)).

Ответ 2

Чтобы понять, почему инструмент статического анализа вызывает предупреждение, нужно подумать о том, как работает статический анализатор. Когда он видит кусок кода, как показано ниже:

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

Непонятно, что может произойти в вызове fun(). Для успешного выполнения метода() на a зависит выполнение некоторых предварительных условий, которые могут не выполняться (или больше не) после вызова fun(). Хотя программист может хорошо знать, что безопасно вызывать метод(), анализатор этого не делает, поэтому он вызывает предупреждение.

Следующее - это только мое собственное мнение. Безопаснее просто предположить, что собственность на все целиком занята 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;
}