将 std::forward_as_tuple() 结果传递给可能从该对象的右值引用成员移动的多个函数?
Posted
技术标签:
【中文标题】将 std::forward_as_tuple() 结果传递给可能从该对象的右值引用成员移动的多个函数?【英文标题】:Passing std::forward_as_tuple() result to multiple functions that may move from that object's rvalue-reference members? 【发布时间】:2012-01-13 04:02:55 【问题描述】:编辑:我认为最可能的用例是创建一个从std::forward_as_tuple()
接收右值引用元组的函数。
想到这个问题的原因是因为我正在检查传递给构造函数初始化器的对象的 成员 以查看它们是否是右值引用(我愿意接受建议告诉我这是错误错误错误...希望遵循经验法则以避免将来发生这种情况,但这就是提出问题的原因)。我突然想到,在稍微不同的上下文中,我最终可能会将具有右值引用成员的对象交给多个函数(或函数对象),我可能控制也可能不控制,这些函数可能会从这些成员中移动。
template<typename... Args>
void my_func(std::tuple<Args...>&& tup)
//if tup's members are rvalue references,
//and this function moves guts from tup members, then...
func_i_dont_control(tup);
//what happens here if moves are done on the same members?
another_func_i_dont_control(std::move(tup));
我查看了Use of rvalue reference members?,以及一些关于右值引用成员的其他讨论,但我不能完全解决这个问题。
我不只是问会发生什么,而是这种情况是否应该/甚至可能发生,以及在传递包含右值引用成员的对象时要记住哪些关键规则。
【问题讨论】:
在你的例子中你不应该转发tup
吗?
嗯,即使 tup 本身不是模板参数,我还会这样做吗?我对完美转发还是有点不满意。
好的,所以如果我有任何不完整的类型作为函数模板参数,请转发它。
实际上,您应该只转发最后一次通话(因此对another_func_i_dont_control
的通话,因为您不希望您的tuple
在您的第一次通话中成为右值(否则它可能会被移动)以后)
std::forward<std::tuple<Args...>>
只是表达std::move
的一种非常冗长的方式。
【参考方案1】:
在此代码中func_i_dont_control
不能静默窃取参数。只有右值绑定到右值引用,并且命名变量不是右值。您的代码要么编译失败,要么func_i_dont_control
(有一个重载)不使用移动语义。
为了让func_i_dont_control
有机会窃取元组(将元组绑定到右值引用),必须使用std::move(tup)
将其显式转换为右值。
(获取左值引用的函数不应该从中移动,否则我们真的无法判断会发生什么。)
编辑 但问题似乎不在于元组本身,而在于它的成员。同样,这些成员不会是右值,因此 func_i_dont_control
需要明确移动它们。我不认为它有这样做的“道德权利”*,除非它接收整个元组作为右值,这在你的函数中不会发生。
* 对于移动语义,您必须遵循某些准则。基本上,您可以将任何内容转换为右值并从中移动,无论您处理的是左值还是右值引用都没有关系。只要您遵循这些准则,移动语义就可以正常工作。如果您开始将事物转换为无视这些准则的右值,则对象将在函数调用等之后开始消失。
【讨论】:
那么传递带有右值引用成员的对象作为参数(给它一个名称)的行为会改变对象的成员吗?std::forward_as_tuple()
是如何工作的,那么如果我将其结果交给最终命名对象的函数?我不是想变得困难,我只是想把它确定下来,我的头在旋转,哈哈。
但是请注意,即使没有func_i_dont_control
将内容移开,正式这也可能会搞砸事情。原则上func_i_dont_control
可以存储一个指向其参数的指针以供以后使用,该指针稍后将指向一个移开的对象。但是我认为这本身就是不好的风格(尤其是不突出记录该行为是不好的风格,这样您就知道不要传递可能会消失的对象;如果记录在案,您就不会调用该函数一个右值引用参数)。【参考方案2】:
元组和可变参数模板的使用似乎偏离了问题的重点,所以让我重新表述一下:
void func(std::unique_ptr<Foo>&& foo)
std::unique_ptr<Foo> bar1(std::move(foo));
std::unique_ptr<Foo> bar2(std::move(foo));
这里发生了什么?好吧,bar2
总是包含一个空指针(bar1
可能,取决于foo
的原始值)。
没有未定义的行为,因为移动构造函数应该让对象处于可用状态需要引用,这正是出于安全原因。所以你可以使用一个从移出的对象,但是它的状态可能不会很有趣:它甚至不需要等同于默认的构造状态,它可以只是一个陈旧的状态。 p>
【讨论】:
这个例子没有std::move(foo)
就无法编译
当你提到让对象处于可用状态时,我立刻想到了一篇很长的文章,内容涉及 r 值引用和完美转发。见this page的底部段落。
@Kristo:是的,在某些情况下可能会出现双重释放问题。幸运的是,在大多数情况下,您都有指向内存/文件/任何内容的指针,因此您会留下一个空指针供析构函数使用。一个有趣的效果是,这不适用于 RAII 类:移动实例后不再有资源,这与习语所暗示的相反。以上是关于将 std::forward_as_tuple() 结果传递给可能从该对象的右值引用成员移动的多个函数?的主要内容,如果未能解决你的问题,请参考以下文章
Javascript 将正则表达式 \\n 替换为 \n,将 \\t 替换为 \t,将 \\r 替换为 \r 等等