【问题标题】:passing heavy objects C++0x传递重物 C++0x
【发布时间】:2011-09-17 20:15:26
【问题描述】:

我有一个函数可以生成一种昂贵的对象(包含向量和非固定大小的地图),所以我真的想避免调用复制 c'tors。

到目前为止,我刚刚从该方法返回了一个 std::shared_ptr 并使用了它,但我认为它很难看,需要 typedefing 才能真正可用。

我知道两件事可能对我有帮助。一是复制省略,二是移动语义。

我的问题是我不知道如何正确使用它们。我的研究告诉我,复制省略完全由编译器完成,而不是标准的一部分。我真的不想仅仅依靠这个。

那么我如何确保调用移动分配并确保它到位以防止编译器复制省略。

ResultSet &&generateResults()
{
    //ResultSet a(); :S
    ResultSet a;
    a.populat(...
    //blah blah blah
    return a;
}

//else where (where the && assignment operator is overloaded
ResultsSet b = generateResults();

在这种情况下,这是最正确的编码方式吗?如果不是,我该如何改进它。我很高兴只使用 C++0x 构造。

顺便说一句:我的编译器是 gcc 4.6

【问题讨论】:

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


【解决方案1】:

可以在 Dave Abrahams 的此博客文章系列中找到您的问题的一个很好的答案:

http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

它涵盖了右值引用、移动构造函数和复制省略之间的交互。有点长,但值得一读:)

简而言之,复制省略具有优先权。如果由于某种原因它没有(或不能)发生,编译器必须首先考虑移动构造函数,最后考虑复制构造函数。

【讨论】:

  • 我已阅读该文章和配套文章。但我仍然留下了上述问题。我应该指出我只使用 C++ 几个月(来自 ansi C)。我相信你会明白有很多东西要学,我只想从一开始就做,而不是在几年后扔掉我现在正在编写的代码。
  • 如果你说的是对的,我不应该把 && 放在返回类型中,剩下的由编译器来做?
  • @111111 是的,您应该删除返回类型中的 &&。否则,您将返回一个悬空引用。
【解决方案2】:

在这种情况下,这是最正确的编码方式吗?

实际上,这是“最不正确”的方式:您正在返回对自动对象的引用。当函数返回时,客户端会收到一个对不再存在的对象的引用。

在这方面左值引用和右值引用没有区别。所以只需摆脱右值引用并按值返回结果。您将获得 NRVO 或移动语义。

【讨论】:

    【解决方案3】:

    如果你不喜欢阅读,这里有一个关于右值和移动语义的视频链接: http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Standard-Template-Library-STL-9-of-n

    【讨论】:

    • 我不介意阅读,只是当一切都是新的时,我觉得无法吸收太多。我现在一定会看那个视频。 :D
    • @111111:你应该看看那个系列的其他视频,它们的信息量很大。
    • 这些视频信息量很大,做这些视频的人很有趣,“如果你记得 auto_prt 就忘了他们吧”。
    • @111111:如果您喜欢阅读这些信息(我知道这对我来说更容易处理),请参阅同一作者的这篇博文:blogs.msdn.com/b/vcblog/archive/2009/02/03/… 剪切+粘贴有点困难如果只是视频屏幕上的图片,请亲自尝试示例。
    【解决方案4】:

    你的研究是错误的。复制省略绝对是标准的。它不是强制性的,但官方允许。在您的函数示例中,我肯定希望它被应用,因为转换相对微不足道。

    其次,你不应该返回一个右值引用——只是按值返回。编译器没有强制要求删除副本——尽管它可能仍然会——但它必须调用移动语义。

    哦,你需要重载移动构造函数,而不是移动赋值运算符,用于特定的代码,当然理想情况下你会同时做。

    ResultSet a();
    

    不定义任何变量,但声明了一个名为 a 的函数,不接受任何内容并返回一个 ResultSet。

    【讨论】:

    • 对不起,在我的例子中是错误的。我知道我只是忘记在我的实际问题中将参数传递给它,但它们与这个问题无关。 '哦,你需要重载移动构造函数'你的意思是它应该是 ResultSet b(generateResults());谢谢你顺便说一句
    • @111111:您问题中的代码是 copy-initialization (如果可用,则使用移动构造函数,否则使用复制构造函数),既不使用赋值运算符。您的评论使用直接初始化,当类型匹配时,它的工作方式完全相同。使用赋值运算符的示例:ResultSet b; b = generateResults(); 请注意,赋值现在与对象构造完全分离。
    • @111111: 不,我的意思是ResultSet b(generateResults());ResultSet b = generateResults(); 之间的区别完全没有——它们都调用移动构造函数ResultSet::ResultSet(ResultSet&&)——而不是ResultSet::operator=(ResultSet&&)
    • 酷,我现在明白了。谢谢。我错误地认为它会构造然后 init'。
    • @111111:assignmentinitialization 不同。而type var = value; 语法是初始化,而不是赋值。
    【解决方案5】:

    你可以了解move constructors,相信会有人为你举个例子。

    但您可能会考虑的另一个选项是unique_ptr。对于您的应用程序,它应该和 shared_ptr 一样工作,并且效率会显着提高。 (不过,您可能仍然需要那些 typedef。)

    【讨论】:

    • 就是这样。我不确定如何安全地返回 unique_ptr 是返回 uptr 还是返回 move(uptr);但我真的想让 RVO 和 C-Elison 工作,因为它可以产生我觉得最干净的代码
    猜你喜欢
    • 1970-01-01
    • 2011-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-05
    • 2011-03-16
    • 1970-01-01
    • 2011-04-21
    相关资源
    最近更新 更多