我有以下情况需要从t1移动构造t2。
不幸的是,这是不可能的(我认为是constness违规)
从foo的调用者那里透明地处理它的正确方法是什么? (即不需要传递值和显式的std :: move)
struct T
{
T() = default;
~T() = default;
T(T&&) = default;
};
T foo(const T& t)
{
T t3;
if (predicate)
return t3;
else
return std::move(t);
}
int main()
{
T t1;
T t2 = foo(t1);
return 0;
}
答案 0 :(得分:6)
从foo的调用者那里透明地处理它的正确方法是什么?
你不能这样做,而那是打算这样做的。左值永远不会透明地移动。
由于移动对象通常处于未知(但合法)状态,因此如果客户端可以将左值作为参数传递给函数,并且允许该函数以静默方式从其中移动,则会很危险。客户端可能会留下一个僵尸对象,甚至不知道它!
因此,该语言强制执行从左值移动必须是有意识的动作的规则,并且应通过调用std::move()
将左值显式转换为右值(实际上是x值)来实现。
在这种情况下,我会说你的函数foo()
- 无论它实际应该做什么 - 应该采用右值引用,客户端应该像这样调用它:
T t2 = foo(std::move(t1));
请注意,一旦将无意义的名称(例如foo()
)转换为反映某个具体程序中某些特定操作的语义的内容,这最后的建议可能不正确。但是,如果不知道该操作的含义是什么,我只能提供关于如何编译代码片段的正式机械建议,以及如何考虑移动语义。
答案 1 :(得分:4)
不需要传递值和显式的std :: move
C ++不会让你在没有至少其中一个的情况下移动东西。这是按设计。
C ++中的基本运动规则是不允许偶然发生。如果你有一个左值,并且你想将它传递给一些可能从它移动的函数,那么你作为调用者必须使用std::move
。这是设计的,因此很明显调用者的意图是对象将被移动。
这样,当您扫描代码时,您可以看到是否意外地以不正确的方式使用了移动值,而不必只看到std::move
的明确使用。
你想要的不是你想要的东西。
答案 2 :(得分:4)
如果你想要足够严重,你可以这样做。为此,您的foo
最终将基本上重新实施std::move
。
struct T
{
T() = default;
~T() = default;
T(T&&) = default;
};
template<class T> struct my_remove_reference {typedef T type;};
template<class T> struct my_remove_reference<T&> {typedef T type;};
// The third part of a normal `remove_reference`, but which we don't need in
// this case (though it'll all still be fine if you un-comment this).
//template<class T> struct my_remove_reference<T&&> {typedef T type;};
template <typename T>
typename my_remove_reference<T>::type &&foo(T &&t) {
return static_cast<typename my_remove_reference<T>::type &&>(t);
}
int main()
{
T t1;
T t2 = foo(t1);
return 0;
}
现在,尽管您可以/可以做到这一点,但其他答案仍然或多或少是正确的 - 即使您可以这样做,您几乎肯定不应该< / em>的。如果你在支持移动构造的真实类上使用了这个foo
(即,实际上将状态从源移动到目标),你最终基本上会破坏你的源对象,即使它仍然可见。
使用您编辑的问题执行此操作,其中foo
可以(但不总是)销毁源对象,但特别有害。使用正确的名称可以清楚地表明foo
确实从源头移动了,可能可能只是勉强合理地做到这一点 - 但是(至少对我来说)一个函数可能会或可能不会破坏其论点的情况似乎比那些可靠的情况更糟糕。显然,像{em>只是上面的foo
的版本与std::move
做同样的事情是没有意义的,但是对输入做了一些其他处理的版本(并且,对于无论什么原因,不能与std::move
一起使用)如果你真的没有其他选择,可能只是几乎不能接受。