将 r 值作为非常量引用传递(VS 警告 C4239)
Posted
技术标签:
【中文标题】将 r 值作为非常量引用传递(VS 警告 C4239)【英文标题】:Passing r-value as non-const reference (VS warning C4239) 【发布时间】:2013-04-25 13:26:50 【问题描述】:我想要做的(使用 C++ lambda)实际上是:
std::vector<MyType> GetTheArray () return something;
const auto DoSomething = [](std::vector<MyType> & array)
//Some processing that involves either sorting the 'array' or setting temporary flags on the items
;
DoSomething (GetTheArray ());
这在标准 C++ 中似乎是不允许的,因为右值不能作为非常量引用传递。
我的问题:
1) 有没有办法使用类型转换来做到这一点,还是我必须创建一个临时变量来存储 GetTheArray () 的结果?
2) C++ 不允许这样做有充分的理由吗?
请注意,从“GetTheArray”返回的“something”是动态构造的数组,而不是存储值。
【问题讨论】:
为什么要对一个在函数返回后立即超出范围的临时数组进行排序? 相关:***.com/q/4084053/951890 有一些方法可以诱使编译器在不涉及创建变量的情况下允许这样做,但正如前文所述,为什么要更改将立即死亡的容器? 2 个可能的原因:a)我需要对其进行排序以便将其与另一个数组进行比较 b)我需要在数据上设置标志,然后再将其传递给另一个函数进行处理(在我的情况下可以将其保存到数据库中) 感谢@Vaughn Cato。我可以看到该问题中有一些相关信息。 【参考方案1】:从 cmets 看来,您想要的是获取一个向量,对其进行破坏性修改(在无法重置原始状态的意义上),然后在内部使用结果。并且您希望这对左值和右值都有效。
下一个问题是在左值的情况下,保存原始容器的代码在函数调用完成后是否需要它,以及它是否需要向量的原始状态。根据答案,您有不同的解决方案:
持有左值的调用者在调用后不再使用它
(或者,持有左值的调用者需要原始状态)
这是最简单的情况。然后你的函数应该按值获取容器。如果调用者有一个左值,它可以std::move
避免复制(它不再关心对象)或 copy 这可能更昂贵但保持原始容器不变。
如果使用 rvalue 调用函数,则副本将被省略或转换为廉价的隐式移动。
持有左值的调用者不需要原始状态,但需要容器
这种情况比较困难,您需要为同一个函数(或 lambda)提供两个重载,一个采用 lvalue 供此调用者使用,另一个采用 rvalue-reference 用于调用者手临时的情况。在这两种情况下,绑定都很便宜。虽然这需要更多代码,但您可以根据另一个重载实现一个重载:
rtype f(std::vector<Data> & ); // for lvalues
rtype f(std::vector<Data> && v) // for rvalues
return f(v); // v is an lvalue here
您正在执行 lambdas 的事实可能会使这稍微复杂一些,但希望不会太多。
【讨论】:
非常有趣。是的,我通过更改我的 lambda 签名以通过 r 值引用获取数组来获得干净的编译。在您看来,这是一个“好的”解决方案,还是应该为移动构造函数和移动赋值运算符保留通过 r 值引用传递参数? @Coder_Dan:当您需要区分左值和右值时,您应该只在界面上使用右值引用。这不仅限于构造函数/赋值,还包括您需要区分的任何操作。 @Coder_Dan:不确定是否足够清楚,如果您仅提供右值引用重载,它将不接受 lvalue 作为参数,因此您限制了采用。如果您开始考虑对于 lvalues 调用者执行std::move
很好,那么您不应该通过 rvalue-reference 获取参数,而是通过 value .
罗德里格斯 - 感谢您的提示。我想我跟着你,看看如果这个函数在公共接口中,或者我也需要接受一个左值引用,那么最好按值传递参数。您还告诉我,在这种情况下,如果我传递包装在对 std::move 的调用中的参数,我可以在传递左值时避免向量的副本,这是一个我以前没有考虑过的有趣概念。在我的例子中,它是一个函数局部 lambda,每次都以相同的方式调用,并为“GetTheArray”函数提供不同的参数。【参考方案2】:
如果您出于某种原因确实想修改 something
,您可以随时将临时文件复制到您的 lambda 中。
const auto DoSomething = [](std::vector<MyType> array) /*whatever*/
您将 lambda 称为编译器的方式可能能够省略副本。 C++11 移动语义对此进行了形式化。
【讨论】:
是的,你是对的。在这种情况下,按值传递“数组”应该没有开销——很明显(嗯,应该是这样)。我想这回答了我的具体问题,尽管可能不适用于更一般的情况。 但是如果你用左值调用会有一些开销,因为它会复制容器。根据这是否重要(或者持有左值的调用者是否不再关心它),您可以采取不同的道路......【参考方案3】:(实际上,@honk 在按值传递方面击败了我。不同的是,我将扩展我的答案以明确使用&&
进行讨论)
删除&
,直接按值传递。
您使用的是 lambda,因此您使用的是 c++11,因此您应该只通过 value 传递它。是的,它会一样快。
C++11 非常善于发现临时文件何时被“复制”。它将用 move 替换副本。然后它会很快,并且可以说会更容易阅读。
因此,即使您可能对&&
和std::move
等一无所知,但可以肯定地说,您应该开始转向通过值而不是通过引用传递事物。它将导致代码更具可读性,如果在正确的时间使用,它不应该导致速度变慢。
基本上,如果您将临时值(例如函数返回的值)传递给函数会很快,但前提是您使用的是标准容器,例如vector
。否则,您需要编写自己的 &&
感知构造函数以利用可能的加速。
更新:如果您决定将其强制为引用,并且/或者您正在使用自定义容器,那么您可以将 &
替换为 &&
。这将允许您传递非常量引用。这称为右值引用。
正如许多其他人所指出的,临时文件将在DoSomething
返回后立即销毁。请记住这一点。这意味着您将希望在数组返回之前完全使用它的内容。也许您将其打印到屏幕上,或者将其存储在数据库中。或者,您可以将数据复制或移动到另一个对象中并返回。
【讨论】:
【参考方案4】:您正在处理从您的底线中的GetTheArray()
调用返回的临时结果。这只能绑定到一个 const 引用,然后对象的生命周期将延长到它所绑定的 const 引用的生命周期(因此是“绑定”)。
即使你可以做你想做的事,你打电话给DoSomething
仍然是一个昂贵的无操作。至少你需要return
来自DoSomething
的东西。
C++ 禁止这样做,因为(自 C++11 起)它具有带有移动语义的右值引用,可以做一些很棒的事情,但不能不返回 something。
【讨论】:
C++ 早在 C++11 之前就禁止这样做了,所以最后一句写出来的意义不大。 rvalue-references(2011 年新增)不能成为 1998 年不允许这样做的原因,除非您相信标准机构的 反因果关系。 好吧,至少有两个主要的实现实现了一个扩展(MSVC 和 Intel 在 Windows 上与 MSVC 兼容),它是有用的。另一方面,回答“为什么禁止”的问题主要是解决鸡和蛋的问题。我想在这种情况下@alexrider 是正确的,它是为了防止像 OP 这样的无操作。 在标准被写下之前,有两个实现允许这样做,Solaris 和 VS。然后他们坚持支持这一点或破坏兼容性。英特尔编译器专门模仿 Windows 中的 VS 和 linux 上的 gcc 的行为,因此在他们的情况下,再次需要:能够将编译器用作本机编译器的替代品。尽管如此,rvalue-references 不能成为在它们成为标准之前的 13 年不允许的原因。 (至少在我简单的头脑中,时间旅行不存在,因果关系统治着世界) 感谢@David Rodriguez。这很有趣。【参考方案5】:如果您需要修改something
数组,您需要返回对它的引用。否则不要通过(非const
)引用DoSomething
不允许它的原因是为了避免您在此处说明的问题。看起来您要修改 something
,但 GetTheArray()
返回的 something
的副本将被更改。这没有多大意义,因为此副本将在 DoSomething
退出后立即删除。
您有 3 种方法可以解决此问题。
1.需要修改something
再修改GetTheArray
返回引用
2.您需要修改将被丢弃的something
的副本DoSomething
完成它的工作,然后更改DoSomething
以按值接受数组。
3.DoSomething
中的something
不需要修改,那么const
引用作为DoSomething
的参数应该使用。
【讨论】:
谢谢@alexrider - 是的,返回的“东西”实际上是一个动态构建并返回的数组。我想我应该更清楚一点。我明白你在做什么,如果没有注意到一个简单的访问器没有通过引用返回值,可能会出现混淆(错误)。以上是关于将 r 值作为非常量引用传递(VS 警告 C4239)的主要内容,如果未能解决你的问题,请参考以下文章
将 opencv Mat 传递给函数时,对非常量的引用的初始值必须是左值