【问题标题】:Yet another optimization with rvalue reference &&. Simply rename fields右值引用 && 的另一个优化。只需重命名字段
【发布时间】:2016-12-08 18:43:39
【问题描述】:

让我们考虑下一段代码:

struct Channel;  // somewhere declared
using iter_t = std::set<Channel>::iterator;

std::set<Channel> myset;
std::pair<iter_t, bool> emplaced = myset.emplace(arg1,arg2);

然后emplaced.first 包含元素的迭代器,emplaced.second 表示元素是否已添加或已存在。 firstsecond 对我来说还不清楚。我想重命名这些字段:

struct ChannelAddedResult
{
    iter_t channelIter;
    bool wasAdded;
public: // --- Functions, constructors ---
    ChannelAddedResult(std::pair<iter_t, bool> &&pa) {
       this->channel = pa.first;
       this->added = pa.second;
    }
 };

此构造函数复制值。右值引用没有任何好处。对吧?
但是如何将std::pair&lt;iter_t, bool&gt; 转换为ChannelAddedResult?这种类型是等价的。所以C风格的对话可能看起来像:

union CarUni {
  std::pair<iter_t, bool>  pair;
  ChannelAddedResult       car;
}
// Use
CarUni u;
u.pair = myset.emplace(arg1,arg2);
auto ch = *u.car.channelIter;
bool a = u.car.wasAdded;

这允许在没有额外副本的情况下实现重命名。可能是 C-casting (ChannelAddedResult)emplaced_pair 会做同样的工作,但它在 C++ 中已被弃用。从类型安全的角度来看,这两种转换是危险的。

这种转换有 C++11 方式吗?

【问题讨论】:

  • 您所说的“C 风格对话”在哪里?这就是C++。 C 风格的转换(在您的示例中 未使用)在 C++ 中不被弃用。总体而言,您的代码看起来不错,并且绝不“从类型安全的角度来看是危险的”。您到底关心什么?
  • std::pair&lt;iter_t, bool&gt; &amp;emplaced == myset.emplace(arg1,arg2); 应该是std::pair&lt;iter_t, bool&gt; &amp;emplaced = myset.emplace(arg1,arg2);
  • std::pair&lt;iter_t, bool&gt; &amp;emplaced == myset.emplace(arg1,arg2); 请避免发布因与您的问题无关的原因而无法编译的代码。我猜有两个错别字,但是......如果有错别字,那么您的代码中有多少其他随机功能是您不打算的错别字?
  • 第二,你认为复制一个标准集迭代器和一个布尔值有多贵?您认为优化有多难?
  • 昂贵 - 这里 2 份不计。但这只是sn-p。在一些更扩展的代码中,即放置 100000000000 个通道可能会减慢应用程序的速度。这个例子实现了重命名字段的想法。我可以想象其他的实现和变化。

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


【解决方案1】:

如果你不想复制,请移动:

ChannelAddedResult(std::pair<iter_t, bool> &&pa)
    : channel(std::move(pa.first))
    , added(std::move(pa.second))
{}

虽然像bools 和迭代器这样的类型并没有真正的区别。

【讨论】:

    【解决方案2】:

    我编写了一个小示例程序来展示我如何尝试一下。 我从您的问题中得到的是,您想将 firstsecond 与您的自定义类型名称(即 channelIterwasAdded)重命名。

    我提取了你的示例代码并用它制作了一个更简单的程序。

    #include <set>
    #include <iostream>
    
    struct Channel
    {
        int _a; // Declaring 2 simple args
        int _b;
    
        Channel(int a, int b) : _a(a), _b(b) {} 
    
        // Need to provide a < operator for custom datatype
        bool operator<(Channel const & rhs) const {
            return (this->_a < rhs._a) || (this->_b < rhs._b);
        }
    };
    
    using iter_t = std::set<Channel>::iterator;
    
    struct ChannelAddedResult
    {
        iter_t _channelIter;
        bool   _wasAdded;
    
        ChannelAddedResult(std::pair<iter_t, bool> && pa) 
                          : _channelIter(pa.first), _wasAdded(pa.second) {}
    };
    
    int main()
    {
        std::set<Channel> myset;
        auto u  = ChannelAddedResult(myset.emplace(5, 6));
        auto ch = *u._channelIter; // Access pair.first
        bool a  = u._wasAdded;     // Access pair.second
    
        std::cout << "ch._a => [" << ch._a << "] ch._b => [" << ch._b << "]\n";
        return 0;
    }
    

    使用 -std=c++11 编译

    $ g++ -std=c++11 set_sample.cpp -o set_sample
    

    输出

    $ ./set_sample
    ch._a => [5] ch._b => [6]
    

    这里要理解的是,ChannelAddedResult(std::pair&lt;iter_t, bool&gt; &amp;&amp; pa) 是一个右值引用,应该传递一个右值才能有效地使用。所以它应该像ChannelAddedResult(myset.emplace(5, 6))一样使用。这里myset.emplace(5,6) 是一个右值。所以不会创建副本。

    相反,如果使用如下,则在两者之间创建了一个无用的副本。将左值传递给右值引用会破坏整个目的。

    auto val = myset.emplace(5,6);
    auto u  = ChannelAddedResult(val); // val is an lvalue
    

    上述代码无法编译。要使其正常工作,您需要将签名升级为 -

    ChannelAddedResult(std::pair<iter_t, bool> const & pa) 
                      : _channelIter(pa.first), _wasAdded(pa.second) {}
    

    现在它可以同时使用右值和左值。 由于我们已经将std::pair 的数据复制到其他变量中,因此右值引用的使用不会对性能增加太多。在这种情况下,最好使用T const &amp; pa 而不是T &amp;&amp; pa

    【讨论】:

    • 对。感谢您提供完整的程序。我试图使我的帖子尽可能短。但据我了解,&amp;&amp;pa 没有任何计量,因为它的工作原理与const&amp;pa
    • @kyb :您的理解不正确。 T &amp;&amp; pa 是一个右值引用,将直接使用myset.emplace(5, 6) 的值(因为它在我的程序中是一个右值)。相反,使用T const &amp; pa 而不是T &amp;&amp; pa 会为其创建一个临时副本,使用它的临时副本,然后再销毁临时副本。
    • 我已经编辑了我的答案以添加更多细节来解决您的疑问。再过一遍。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-02
    • 1970-01-01
    • 1970-01-01
    • 2019-06-12
    • 2015-01-17
    相关资源
    最近更新 更多