【问题标题】:Can a virtual function be a candidate to RVO (return value optimization)?虚函数可以成为 RVO(返回值优化)的候选者吗?
【发布时间】:2014-12-04 10:48:07
【问题描述】:

C++ 编译器是否能够将 RVO 应用于虚函数?

在这种情况下:

class AbstractReader
{
//...
public:
    virtual std::vector<float> getFloatVector() = 0;
//...
}

class XmlReader : public AbstractReader
{
//...
public:
    virtual std::vector<float> getFloatVector()
    {
        std::vector<float> result;

        //Do some parsing here...

        return result;
    }
//...
}



class BinaryReader : public AbstractReader
{
//...
public:
    virtual std::vector<float> getFloatVector()
    {
        std::vector<float> result;

        //Do some decoding here...

        return result;
    }
//...
}

RVO 可以应用于return result; 行吗?我猜不会。

那么,在这种情况下,std::move(result) 是返回大型容器的方法吗?

谢谢

【问题讨论】:

  • 你能澄清你的问题吗?你经常返回虚函数吗?
  • @juanchopanza:我认为问题在于 RVO 是否在虚函数内工作,即对于虚函数可能返回的任何内容,而不是 RVO 在返回虚函数时是否工作。 (而且我认为这在原则上不应该起作用)
  • @Damon 我也这么认为,但最好让 OP 解释他们真正要问的意思。
  • 虚拟机制只是用来选择调用哪个函数。在选择之后,我认为无论选择什么实际功能都会在适当的情况下将 RVO 编译到其中。我没有看到编译器在处理每个特定函数时需要知道其他函数做了什么。
  • 我不知道 RVO 是如何工作的,但我猜想链接器必须知道 RVO 是否发生。 RVO 机制对调用者完全透明吗?

标签: c++ virtual move rvo


【解决方案1】:

是的,编译器可以执行 RVO。我编写了一些测试代码并通过godbolt 运行它:

struct M {
  M();
  M(const M&);
  M(M &&);
  ~M();
  double * ptr;
};

M getM();

struct A {
  virtual M foo() = 0;
};

struct B : A {
  virtual M foo() override;
};

M B::foo(){
  M m;
  return m;
}

struct C : B {
  virtual M foo() override;
};
M C::foo(){
  M m = getM();
  return m;
}

A* getA();

int main(){
  A* p = getA();
  M m = p->foo();
}

g++ -O3 产生

B::foo():
    pushq   %rbx
    movq    %rdi, %rbx
    call    M::M()
    movq    %rbx, %rax
    popq    %rbx
    ret
C::foo():
    pushq   %rbx
    movq    %rdi, %rbx
    call    getM()
    movq    %rbx, %rax
    popq    %rbx
    ret
main:
    subq    $24, %rsp
    call    getA()
    movq    (%rax), %rdx
    movq    %rax, %rsi
    movq    %rsp, %rdi
    call    *(%rdx)
    movq    %rsp, %rdi
    call    M::~M()
    xorl    %eax, %eax
    addq    $24, %rsp
    ret

反汇编中明显缺少对M的复制或移动构造函数的任何调用。


此外,标准中规定复制省略标准的段落没有区分虚拟成员函数和非虚拟成员函数,并且每当满足复制省略标准时,return 语句的重载决议“首先执行为如果对象是由右值指定的”。

也就是说,在一个函数中

M foo() {
    M m = /*...*/;
    return m;
}

如果由于某种原因无法进行复制省略,并且移动构造函数可用,return m; 将始终调用移动构造函数而不是复制构造函数。因此,如果您要返回局部变量,则无需使用 std::move 作为 return 语句。

【讨论】:

    【解决方案2】:

    如果你return std::move(result);,你什么都得不到,你可能会失去。所以不要这样做。

    您无法获得任何东西,因为标准明确规定“如果满足 RVO 条件,或者您正在返回一个参数,请先尝试以 rvalue 的形式返回,并且只有当它无法编译时,才返回作为左值。所以即使你return result;,编译器也强制首先尝试return std::move(result);

    您可能会输,因为 return std::move(result); 专门阻止 RVO(如果它适用的话)。

    【讨论】:

    • 其实在RVO不可行的情况下,如果返回的类型是可移动的并且有很大的底层缓冲区,那么你可以获得很多。这也不是问题的答案。
    • @galinette 不,如果由于某种原因无法执行复制省略,return result; 仍会在可能的情况下执行移动。
    • @galinette 好吧,如果 RVO 不可行,那么 result 既不是局部变量也不是函数的参数,因此在这种情况下使用 std::move 无论如何都不是一件容易的事。你问的是 RVO,所以我假设满足 RVO 标准的情况。
    • @galinette 关于您添加到问题中的示例,即使 RVO 没有发生(它始终是可选的),也符合 RVO 的 标准, 因此编译器必须先尝试右值重载。
    猜你喜欢
    • 1970-01-01
    • 2013-10-16
    • 2013-01-03
    • 1970-01-01
    • 1970-01-01
    • 2015-09-09
    • 2017-04-18
    • 1970-01-01
    相关资源
    最近更新 更多