对象传递给 std::move 但没有移出?
Object passed to std::move but not moved from?
我正在审查这样的代码,其中 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
满足评论中的约定,
- 这个有用吗?
- 是UB吗?
std::move
在 ???
是空操作吗?
(可能这个特定的实例可以重构以避免微妙之处,但我还是想问一下我自己的理解)。
这是您的静态分析工具发出的虚假警告。
- Does this work?
是的,MaybeConsume
正在按照评论所说的进行操作。它仅在 some condition
为真时才取得其参数的所有权(假设 Consume
实际上确实从其参数中移动了 construct/assign)。
std::move
确实只是一个花哨的 static_cast<T&&>
所以 MaybeConsume(std::move(a))
没有转移所有权,你只是绑定了对 MaybeConsume
参数的引用。
- Is it UB?
不,如果 MaybeConsume
表明它已取得其参数的所有权,则您没有使用 a
。
- Is
std::move
at ??? a no-op?
嗯,这是一个空操作,因为它只是一个 static_cast
,但如果你想问它是否不必要,那么,不,它不是。在 MaybeConsume
的正文中,a
是一个左值 because it has a name。如果 Consume
的签名是 void Consume(A&&)
,那么没有 std::move
.
代码将无法编译
从您展示的示例用法来看,您似乎不应该使用纯右值参数调用 MaybeConsume
,因为如果函数 returns false
。如果是这样,那么您应该将其签名更改为 bool MaybeConsume(A&)
。这可能会让您的静态分析工具满意,因为这样您就可以编写 if (!MaybeConsume(a))
.
要理解为什么静态分析工具会发出警告,需要从静态分析器的角度来思考。当它看到如下一段代码时:
A a;
fun(std::move(a);
a.method();
尚不清楚 fun() 调用中会发生什么。要在 a 上成功执行 method() 取决于是否满足一些先决条件,这些先决条件在调用 fun() 后可能不会(或不再)成立。虽然程序员可能很清楚调用 method() 是安全的,但分析器不知道,因此它会发出警告。
以下仅代表本人观点。更安全的做法是假设 a 的所有权完全由 fun() 获取。为防止混淆,最好强制执行 borrow-and-return 风格,把它想象成朋友向你借书,你不(不能)使用那本书,直到它是 return编辑。因此,永远不要冒险不小心调用到那时应该 "dead" 的对象。
见下面的演示代码:
#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;
}
我正在审查这样的代码,其中 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
满足评论中的约定,
- 这个有用吗?
- 是UB吗?
std::move
在???
是空操作吗?
(可能这个特定的实例可以重构以避免微妙之处,但我还是想问一下我自己的理解)。
这是您的静态分析工具发出的虚假警告。
- Does this work?
是的,MaybeConsume
正在按照评论所说的进行操作。它仅在 some condition
为真时才取得其参数的所有权(假设 Consume
实际上确实从其参数中移动了 construct/assign)。
std::move
确实只是一个花哨的 static_cast<T&&>
所以 MaybeConsume(std::move(a))
没有转移所有权,你只是绑定了对 MaybeConsume
参数的引用。
- Is it UB?
不,如果 MaybeConsume
表明它已取得其参数的所有权,则您没有使用 a
。
- Is
std::move
at ??? a no-op?
嗯,这是一个空操作,因为它只是一个 static_cast
,但如果你想问它是否不必要,那么,不,它不是。在 MaybeConsume
的正文中,a
是一个左值 because it has a name。如果 Consume
的签名是 void Consume(A&&)
,那么没有 std::move
.
从您展示的示例用法来看,您似乎不应该使用纯右值参数调用 MaybeConsume
,因为如果函数 returns false
。如果是这样,那么您应该将其签名更改为 bool MaybeConsume(A&)
。这可能会让您的静态分析工具满意,因为这样您就可以编写 if (!MaybeConsume(a))
.
要理解为什么静态分析工具会发出警告,需要从静态分析器的角度来思考。当它看到如下一段代码时:
A a;
fun(std::move(a);
a.method();
尚不清楚 fun() 调用中会发生什么。要在 a 上成功执行 method() 取决于是否满足一些先决条件,这些先决条件在调用 fun() 后可能不会(或不再)成立。虽然程序员可能很清楚调用 method() 是安全的,但分析器不知道,因此它会发出警告。
以下仅代表本人观点。更安全的做法是假设 a 的所有权完全由 fun() 获取。为防止混淆,最好强制执行 borrow-and-return 风格,把它想象成朋友向你借书,你不(不能)使用那本书,直到它是 return编辑。因此,永远不要冒险不小心调用到那时应该 "dead" 的对象。
见下面的演示代码:
#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;
}