【问题标题】:Efficient way to build and return a complicated result dataset构建和返回复杂结果数据集的有效方法
【发布时间】:2018-02-20 12:40:37
【问题描述】:

通常,一个函数会构造一个复杂的结果,例如对象的vector。在普通的、未优化的 C++ 中,这很容易导致许多中间副本。我想知道我应该如何构造这样一个对象以避免不必要的复制。

以下函数显示了我想要做什么以及如何天真地实现它。 db 上的调用只是伪代码,大致展示了函数的结构。

vector<Result> get_result(Query q){
    vector<Result> ret;
    db.start_query(q); 
    while(db.has_more()){
        int i = db.get_next_int("i");
        string s = db.get_next_str("s");
        Result result(i, s);
        ret.push_back(result); // copy on push
    }
    return ret; // copy on return
}

考虑到现代 C++11 或更新版本,我应该如何实现这种功能并移动语义?可以更改函数的签名,以便vector&lt;Result&gt; 可以成为捕获结果的输入参数。

【问题讨论】:

  • 如果你有信息,ret.reserve(final_size) 可能会避免使用重新分配的副本。
  • 只是猜测,但我会把钱花在start_query 上,这比你在这段代码中可以做的任何事情都要多。
  • 没有“返回时复制”return ret; 语句选择vector 的移动构造函数,因为ret 被视为rvalue 这里。有关详细信息,请参阅 C++11 标准的第 12.8/32 节。我要添加的唯一内容是按照 Jarod42 的建议添加到 reserve 向量内存并切换到 emplace_back 以防止复制 result
  • 通过引用返回。这减少了复制大型结构的内容的需要。您也可以使用智能指针传递。
  • @ThomasMatthews 什么?你听说过复制省略和移动语义吗? OP 的 get_result 函数是一个经典示例,其中按值返回无疑是最佳实践。

标签: c++ c++11 move-semantics


【解决方案1】:

您所能做的就是使用emplace_back就地构造result,从而避免复制,而且由于NRVO,返回时可能不会有复制。

ret.emplace_back(i, db.get_next("s"));

无论如何,由于 VS2017 中 vector 的大小是 32 字节,所以它非常轻量级并且移动操作很快,因此如果您使用的数据库可能并不重要。

【讨论】:

  • 只是为了澄清,这是 NRVO,而不是 RVO。 NRVO 是可选的(即使在 C++17 中),但是,它可能会被所有主流编译器应用。即使没有,vector 的 move 构造函数也会被调用两次,这很便宜。
  • @DanielLangr 感谢您的澄清。我将 RVO 更改为 NRVO。我也让最后一段更清楚了。但是,如果您想回答这个问题,您将获得一些积分)
  • 我只想复制你的答案:)。也许,我会将“不会有副本” 改为 可能 不会有副本”,因为再一次,NRVO 不是强制性的。
【解决方案2】:

在 C++17 中甚至不保证命名返回值优化。

所以如果你想确定没有不必要的值拷贝,那么通过引用传递返回向量:

void get_result(Query q, vector<Result>& foo)

然后写

foo = std::move(ret);

作为最后的陈述。

我一直小心避免return std::move(ret):见When should std::move be used on a function return value?

【讨论】:

  • 即使不应用 NRVO,也不会产生不必要的副本。在return ret; 语句中,ret 被视为rvalue,因此将调用移动构造函数,这很快。在这种情况下,我永远不会喜欢通过引用传递。
猜你喜欢
  • 1970-01-01
  • 2019-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
  • 2016-03-23
  • 1970-01-01
相关资源
最近更新 更多