右值引用 && 的另一个优化。只需重命名字段

Posted

技术标签:

【中文标题】右值引用 && 的另一个优化。只需重命名字段【英文标题】: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 个通道可能会减慢应用程序的速度。这个例子实现了重命名字段的想法。我可以想象其他的实现和变化。 【参考方案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 会为其创建一个临时副本,使用它的临时副本,然后再销毁临时副本。 我已经编辑了我的答案以添加更多细节来解决您的疑问。再过一遍。

以上是关于右值引用 && 的另一个优化。只需重命名字段的主要内容,如果未能解决你的问题,请参考以下文章

❥关于C++之右值引用&移动语义┇移动构造&移动复制

右值引用&&

重用“&&”标记以进行右值引用的基本原理?

右值引用重载 && 运算符

对临时声明的右值引用

我应该通过右值引用返回一个右值引用参数吗?