【问题标题】:C++11 expensive rvalue temporaryC++11 昂贵的临时右值
【发布时间】:2014-11-16 12:18:11
【问题描述】:

我有一些轻物体可以推动和操纵,然后我想将它们包含在更复杂的物体中。有一个查找表应该保持不变。这个想法看起来很简单,但是这样做的一行 - b += c(a); - 创造了一个昂贵的临时性。

#include <vector>
static int count;

struct costly {
    /* std::map<std::string, int> and whatnot */
    int b;

    ~costly() { count++; }
     costly(int b): b(b) { }
     costly &operator+= (costly &rhs) { b += rhs.b; return *this; }
};

/* Note the assumption above constructor exists for rhs */
costly operator* (const costly &lhs, costly rhs) {
    rhs.b *= lhs.b; return rhs;
}

struct cheap {
    /* Consider these private or generally unaccessible to 'costly' */
    int index, mul;

    cheap(int index, int mul): index(index), mul(mul) { }
    costly operator() (const std::vector<costly> &rhs) {
        /* Can we do without this? */
        costly tmp = rhs[index] * mul; return tmp;
    }
};

int main(int argc, char* argv[]) {
    std::vector<costly> a = {1, 2}; costly b(1); cheap c = {1, 2};
    /* Above init also calls the destructor, don't care for now */
    count = 0;
    b += c(a);
    return count;
}

我一直在阅读 RVO 和 C++11 的右值,但还不能完全理解它们,以完全消除引入的中间值。上面只创建了一个,因为 rhs 的构造函数可用。最初我有这个;

costly operator* (costly lhs, int rhs) {
    lhs.b *= rhs; return lhs;
}

/* ... */

costly operator() (const std::vector<costly> &rhs) {
    return rhs[index] * mul;
}

但是,与我的直觉相反,导致 count 甚至是 2。为什么编译器没有得到我的意图?

【问题讨论】:

  • 如何将您的查找表更改为shared_ptr&lt;const map&lt;string, int&gt;&gt;,并仅在需要更改时复制(并替换它)?这将使副本便宜。

标签: c++ rvo


【解决方案1】:

RVO 不适用于函数参数,因此您的 * 运算符正在禁止它。为了启用 RVO,您需要参数的本地副本。然后,您可以通过提供采用右值引用的重载进行优化(前提是 costly 具有有效的移动复制构造函数)。例如,

costly operator*(const costly& c, int i)
{
  costly ret = c;
  ret += 1;
  return ret;
}

costly operator*(costly&& c, int i)
{
  costly ret = std::move(c);
  ret += 1;
  return ret;
}

【讨论】:

  • 虽然这是真的,但我认为这不是全部答案。 OP 无法提供右值引用,因为源是 const std::vector&lt;costly&gt;&amp;
  • @ChrisDrew 然后将使用第一个重载。这具有 RVO,因此比 OP 的版本需要更少的副本。
  • 少一份,是的。但是还剩下一份。
【解决方案2】:

这是一种完全不同的方法,与您是否通过 RVO 进行优化是正交的,但这里是:

由于使复制变得昂贵的内部数据成员或多或少是 const,为什么不直接避免复制该特定成员?

如果你像这样更改costly

struct costly {
    shared_ptr<const map<string, int>> lookup_table;
    int m;
    ...
};

复制变得便宜得多。请注意,指向表的指针是非常量的,即使它指向的映射是 const。

Sean Parent 对此进行了很好的讨论,关于他们如何在 Photoshop 中实现历史记录和图层。由于带宽有限,我目前无法查找 URL。

【讨论】:

【解决方案3】:

我认为部分问题在于算术运算符最适合复制相对便宜的值类型。如果你想完全避免复制costly,我认为最好避免重载这些运算符。

它可能会在 costly 上添加太多逻辑,但您可以添加一个函数来执行您想要的操作而无需复制:

void addWithMultiple(const costly& rhs, int mul) {
    b += rhs.b * mul;
}

然后可以像这样被cheap 调用:

void operator() (costly &b, const std::vector<costly> &a) {
    b.addWithMultiple(a[index], mul);
}

但它是对您开始的内容进行了相当大的重构,因此可能无法满足您的所有需求。

【讨论】:

  • 我早些时候解决了它,因为 b.add(c, a) 或类似的东西,但被 cheap 搞砸了,必须允许 costly 访问。您的建议也有效,但我仍然希望它返回结果而不是修改参数。
  • 如果您返回一个结果,该结果与您当前拥有的任何其他结果根本上是不同的实例,因此无法避免复制,RVO 也无能为力。
  • 这很令人失望。不能返回什么都没有的右值。
猜你喜欢
  • 2011-12-06
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 2018-03-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多