我正在审核一些像这样的代码,其中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
满足评论中的合同,
std::move
???
是否为无操作?(可能这个特殊的例子可以重构以避免微妙,但我仍然想要求我自己的理解。)
答案 0 :(得分:14)
这是静态分析工具发出的虚假警告。
- 这有用吗?
醇>
是的,MaybeConsume
正在做评论所说的。当some condition
为真时(假设Consume
实际上确实从其参数中移动了构造/赋值),它只取得其参数的所有权。
std::move
确实只是一个花哨的static_cast<T&&>
,因此MaybeConsume(std::move(a))
没有转让所有权,您只需绑定对MaybeConsume
参数的引用
- 是UB吗?
醇>
不,如果a
表示已承担其参数的所有权,则您不会使用MaybeConsume
。
- ??? 是
醇>std::move
a no-op?
嗯,这是一个没有操作,因为它只是一个static_cast
,但如果你想问它是否不必要,那么,不,它不是&#39;吨。在MaybeConsume
的正文中,a
是左值because it has a name。如果Consume
的签名为void Consume(A&&)
,则代码不会在没有std::move
的情况下编译。
根据您显示的示例用法,似乎您不应该使用prvalue参数调用MaybeConsume
,因为如果函数调用者应该以某种其他方式使用该参数返回false
。如果这是真的,那么您应该将其签名更改为bool MaybeConsume(A&)
。这可能会使您的静态分析工具满意,因为这样可以编写if (!MaybeConsume(a))
。
答案 1 :(得分:0)
要理解为什么静态分析工具会发出警告,需要考虑静态分析器的方式。当它看到如下代码时:
A a;
fun(std::move(a);
a.method();
目前尚不清楚fun()调用中可能会发生什么。要在a上成功执行method()取决于满足某些先决条件,在调用fun()之后可能不会(或不再)保持。虽然程序员可能知道调用method()是安全的,但分析器却没有,所以它会发出警告。
以下只是我自己的看法。假设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;
}