【问题标题】:Passing rvalue as reference传递右值作为参考
【发布时间】:2016-04-11 20:51:57
【问题描述】:

我有一些从我的 svn 存储库下载的 Qt 代码。我已经有一段时间没有研究它了,但我确信它曾经编译过。

我有一个新版本的 Qt 和编译器(与上次一样)。我目前的编译器是:mingw 4.9.2 32-bit。

这是我的问题代码:

QByteArray dataBlock = audioTestFile.read(PACKET_SIZE_TO_ENCODE);
// This line is the issue
uint8Vect_t testVect = encodeData(uint8Vect_t(dataBlock.begin(), dataBlock.end()));

地点:

typedef std::vector<uint8_t> uint8Vect_t;

uint8Vect_t encodeData(uint8Vect_t &dataBuff);

所以你可以在这里看到我有一个函数 encodeData() ,它接受一个参数 uint8Vect_t &amp; (通过 ref 传递)。我正在传递一个使用来自QByteArray dataBlock 迭代器(我已经测试过)的std::vector 构造函数(其中一个需要两个迭代器)创建的临时变量(我认为是一个右值)。

但是,我得到了错误:

../audioTest/txaudiostream.cpp:在成员函数'void CTxAudioStream::playFile()': ../audioTest/txaudiostream.cpp:212:94: 错误:没有匹配的调用函数 'CTxAudioStream::encodeData(uint8Vect_t)' uint8Vect_t testVect = encodeData(uint8Vect_t(dataBlock.begin(), dataBlock.end())); ^ ../audioTest/txaudiostream.cpp:212:94:注意:候选人是: ../audioTest/txaudiostream.cpp:36:13:注意:uint8Vect_t CTxAudioStream::encodeData(uint8Vect_t&) uint8Vect_t CTxAudioStream::encodeData(uint8Vect_t &dataBuff) ^ ../audioTest/txaudiostream.cpp:36:13:注意:没有已知的参数 1 从 'uint8Vect_t {aka std::vector}' 到 'uint8Vect_t& {aka std::vector&}'的转换

基本上是说我无法从 uint8Vect_t 转换为 uint8Vect_t&。但是,如果我将 uint8Vect_t 类型的变量传递给函数(而不是构造函数/临时变量的返回值),那么这可以正常工作。

我认为在 c++11 中你可以传递右值。但我显然在这里遗漏了一些东西。谁能解释一下:

  1. 为什么这是错误的?
  2. 什么是高效/优雅(可读)的解决方案?

【问题讨论】:

  • 你的旧编译器是什么? MSVS?
  • 您不能通过非常量引用传递临时(右值)。根据您期望的语义(所有权、可变性等),按值传递或通过 const 引用传递。
  • @NathanOliver 很有可能是,我在处理大量不同的编译器...但我不能 100% 确定...会有区别吗? surly 它的语法是否正确? (不是迂腐,这是一个严肃的问题)
  • 代码错误,所以它之前编译的事实告诉我们,之前的编译器很可能是 MSVC(它编译了错误代码的特定示例)。
  • @code_fodder 我在答案中添加了解释。

标签: c++ qt c++11 rvalue-reference


【解决方案1】:

你的问题是

uint8Vect_t encodeData(uint8Vect_t &dataBuff);

这里你引用了uint8Vect_t。这适用于普通变量,但 uint8Vect_t(dataBlock.begin(), dataBlock.end()) 是一个临时对象,不能绑定到左值引用。

如果encodeData() 不更改dataBuff,那么最简单的解决方案是采用可以绑定到临时对象的const &amp;

uint8Vect_t encodeData(const uint8Vect_t &dataBuff);

如果您必须更改 dataBuff 的内容,那么您将不得不编写另一个版本的 encodeData(),它采用右值引用

uint8Vect_t encodeData(uint8Vect_t &&dataBuff);

这将允许函数绑定到临时向量,您可以像处理普通向量一样在函数中处理它。


我相信您看到这个的原因是您的旧编译器是 Microsoft Visual Studio 的一个版本。 MSVS 有一个默认开启的非标准扩展,它允许临时对象绑定到左值引用。你可以阅读更多关于它的信息:Non-const reference bound to temporary, Visual Studio bug?


添加它是为了向您展示如何更改 encodeData() 以获取右值引用,而无需编写新函数。

#include <iostream>
#include <vector>

std::vector<int> modify(std::vector<int>& foo)
{
    for (auto & e : foo)
        e *= 2;
    return foo;
}

std::vector<int> modify(std::vector<int>&& foo)
{
    return modify(foo);
}


int main()
{
    std::vector<int> foo = modify({ 1,2,3,4,5 });
    for (const auto & e : foo)
        std::cout << e << " ";
}

Live Example

在上面的例子中modify({ 1,2,3,4,5 })调用modify(std::vector&lt;int&gt;&amp;&amp; foo)然后在函数foo是一个lvaue。然后我们返回将“新”左值传递给modify(std::vector&lt;int&gt;&amp; foo) 的结果,modify(std::vector&lt;int&gt;&amp; foo) 然后返回一个修改后的向量。

【讨论】:

  • 感谢您的精彩解释,+1 ...我认为由于 c++11 已经在使用移动语义...但我也完全忘记了新语法...谢谢v. 很多
  • 感谢您提供 MSVC 信息...这似乎有些道理,因为我确信我将这段代码保持在正常工作状态(加上我的代码中相当基本的部分,所以很早就开始工作了).. . 但是它的工作原理很奇怪,因为我修改了这些值 :o .. 一定只是部分运气.. 在我听到的代码中使用从来都不是一件好事; ) 回答问题的所有部分以及 MSVC 的事情......
  • @code_fodder 谢谢。我还添加了一个示例,说明如何重载 encode() 以获取右值,而无需重写一堆代码。
【解决方案2】:

任何函数的返回值都是临时对象(右值),不能将临时对象作为引用传递。

下面的代码将产生与我们试图将“saurabh”(临时对象)作为引用类型传递相同的错误。

void fun(string& name){
  //statements;
}

int main(){
    fun("Saurabh");
    return 0;
}

【讨论】:

    【解决方案3】:

    当你使用

    encodeData(uint8Vect_t(dataBlock.begin(), dataBlock.end()))
    

    你传入函数的向量是一个临时对象,引用不能绑定到临时对象。

    如果函数不修改参数,简单的解决方案是使其成为对 constant 对象的引用:

    uint8Vect_t encodeData(uint8Vect_t const& dataBuff);
    

    对常量对象的引用可以绑定到临时对象。

    【讨论】:

    • 好的,我想我现在明白了(从你的回答和其他 cmets)... const 的东西现在似乎很明显(经过解释!),+1
    • 刚刚重新检查了......我正在修改dataBuf,所以我需要通过我认为的移动或复制。
    • @code_fodder 然后你基本上有两种解决方案:创建一个临时变量并将其传递给函数,或者创建函数的两个重载,一个引用一个非常量对象(它可以修改传递的参数),一个引用一个常量对象(它不能修改参数)。
    • 非常感谢:)
    【解决方案4】:

    你想对你传入的对象做什么?

    当您将其视为uint8Vect_t &amp;dataBuff 时,这应该意味着您想要对其进行持久修改,如果它是暂时的,那将毫无意义。

    当你把它当作uint8Vect_t const&amp;dataBuff 时,这应该意味着你想从中复制而不是修改它,这可能就是你想要的

    当您将其视为uint8Vect_t dataBuff 时,这意味着您需要自己的本地临时副本,随意使用然后丢弃,这应该足够重要,值得复制成本。

    当您将其视为uint8Vect_t &amp;&amp;dataBuff 时,这意味着您希望对调用者有效承诺在您完成后丢弃的临时对象进行非持久修改(例如内容窃取)。

    最后一个选项是 C++11 中用于传递右值的新选项。

    【讨论】:

    • 感谢您的详细回答,+1。我正在修改数据(以为我不是,但经过第二次查看后我是)......所以你是对的, const 对我不起作用,我必须使用移动语义:)
    猜你喜欢
    • 2011-12-15
    • 2014-02-10
    • 1970-01-01
    • 2020-09-04
    • 2019-12-28
    • 2014-03-05
    • 1970-01-01
    • 2023-03-24
    • 2013-05-28
    相关资源
    最近更新 更多