问题描述
假设class Object
满足 MoveConstructible 和 MoveAssignable 的要求,但不满足 copyConstructible 或的要求copyAssignable (即其中包含unique_ptr的PIMPL)。然后,我有两个消费者:
bool consumer1(Object && arg) {
if(arg ...) { // arg satisfies some condition
Object b {std::move(arg)};
// some processing
return true;
}
return false;
}
bool consumer2(Object &&arg) {};
int main() {
Object arg;
// some arg initialization here
if(!consumer1(std::move(arg)))
consumer2(std::move(arg));
}
即我想移动一个Object实例到consumer1(),如果由于某种原因它不接受它,我将该实例移到consumer2()。
或者,我可以检查实例的状态(即是否从实例中移出),而不用依赖返回值。
我知道that
std :: move不会移动任何东西。
尽管如此,由于两次举动,我感到不舒服。
解决方法
这个问题是很主观的,但是我会尽力回答。
虽然您编写的代码在技术上没有任何错误,但由于该代码可以正确编译并正常运行,因此从更高层次上讲这是一种设计气味。
对对象std::move
的明显重复调用限制了开发人员推断该对象寿命的能力,这可能会降低可读性。静态分析工具也可以将这种移动标记为“移动后使用”,尽管实际上该对象尚未真正就位(从技术上讲这不会错)。
我想到了几个清理选项,但是它们是完全可选的,并且取决于您的实际用例:
- 将
std::move
的条件分解为单独的支票,或者 - 使用后备模式
打破条件
如果您可以打破consumer1
消费arg
的条件,则可以在首次尝试消费之前进行显式测试-允许您拥有两个明显不同的分支到{{1 }}独立地:
std::move(arg)
后退模式
另一种可能的重构方式是使用回退模式,在该模式中,您设计代码时会在失败时发生依赖项注入的“回退”。
如果这只是简单的原始函数,那么简单的例子可能是:
if(can_consume(arg)) {
consumer1(std::move(arg));
} else {
consumer2(std::move(arg));
}
但是,这也可以扩展到完整接口,其中扩展了using FallbackFunction = void(*)(Object&&);
void consume1_or(Object&& arg,FallbackFunction function)
{
if(arg ...) { // arg satisfies some condition
Object b {std::move(arg)};
// some processing
return
}
// fallback on failure
(*function)(std::move(arg));
}
...
// use
consume1_or(std::move(arg),&consume2);
的对象在构造时也接受Consumer
以便在失败时将对象传递给对象。
正如您所编写的,当1
返回false时,arg
仍处于有效状态,因此您的代码没有任何错误(即使不是,也仍然存在)没错)。