【问题标题】:Does RVO work with "new"?RVO 是否与“新”一起使用?
【发布时间】:2015-08-07 07:03:12
【问题描述】:

复制省略在这种情况下会起作用吗?换句话说,具有复制省略的现代编译器是否避免在这里调用任何复制构造函数?

class BigObj{};

BigObj fun()
{
  BigObj b;
  return b;
}
int main()
{
  BigObj *pb = new BigObj(fun());
}

我的目标是将对象存储在指针中。对象由函数返回。我想保存它而不复制它。

我不能使用 c++11

【问题讨论】:

  • @juanchopanza,他可能在提问时混淆了语言。他的意思是,在创建new BigObj(..) 时,不需要复制从fun() 返回的对象。而是使用相同的对象。
  • 感谢指出这句话很容易被误解,希望我能改正。
  • @ddriver,RVO 在fun() 的情况下确实会启动,但在调用new BigObj(..) 时会创建一个新副本。这可以通过在class BigObj 中使用移动构造函数来避免。 See my answer.
  • “复制省略”和“RVO”不是一回事。 “复制省略”删除中间临时。 “RVO”(或更准确地说,在这种情况下是“NRVO”)可能会完全删除 b 和所有复制。那么,这个问题应该是关于什么的呢?复制省略?还是 [N]RVO?
  • 或许问题是:如果我们在fun里面打印&b,会不会和pb一样?如果是,为什么?

标签: c++ c++03 rvo nrvo


【解决方案1】:

IMO 并不完全清楚您的目标是什么。如果您想使用动态分配,那么您的函数应该是:

BigObj * fun()
{
  return new BigObj;
}
int main()
{
  BigObj *pb = fun();
}

...省去你自己的麻烦。

与答案的先前版本相反,事实证明,只要在可以彻底分析的静态上下文中,编译器就可以省略大量工作:

class C {
public:
    C() {qDebug() << "C created";}
    C(const C & o) { qDebug() << "C copied"; }
};

C foo() {
    C c;
    qDebug() << &c;
    return c;
}

...
    C c = C(foo()); // note that `C c = ` is also copy construction
    qDebug() << &c;

输出验证两个实例具有相同的地址,因此即使在本地的上下文中,该实例实际上也没有存储在foo的堆栈帧中。

改为:

C * cp = new C(foo());
qDebug() << cp;

令我惊讶的是,它还输出了相同的地址,同时省略了按值返回的副本和复制构造函数。 foo中的c直接在内存块中构造,由new分配。

总之,C++ 编译器在分析和执行所有可能的优化方面非常聪明。

分别在第一种和第二种情况下关闭优化:

C created
0x28fd97
C copied
C copied
C copied
0x28fdeb

...

C created
0x28fd97
C copied
C copied
0x34fd00

【讨论】:

  • 这是一个非常令人惊讶的体验,不是吗?我不会从函数中返回指针,除非必要,否则我不会首先使用指针,但我使用的遗留代码会做很多事情。
  • @BarnabasSzabolcs 考虑到 NRVO 的实施方式,我并不感到惊讶。这也与编译器的智能程度没有太大关系 IMO:NRVO 通常是通过向函数添加一个附加参数来实现的,该参数传递构造返回值的位置。对于被调用者 (fun) 来说,无论是堆栈内存还是堆内存都无关紧要。事实上,clang++ 在这种情况下应用 NRVO,即使没有内联 fun-O0
【解决方案2】:

RVO 是标准允许但没有特别要求的内容之一。也就是说,大多数现代编译器(至少启用了适当的优化设置)都会实现它。但是,如果您想要保证,则需要阅读编译器文档。

由于目标是动态分配对象,我将简单地更改示例,以便被调用的函数进行动态分配。而不是(OP的代码);

BigObj fun()
{
    BigObj b;
     //   presumably the point of fun() is that some initialisation
     //     of b occurs here
    return b;
}
int main()
{
    BigObj *pb = new BigObj(fun());
}

我会简单地使用

BigObj *fun()
{
    BigObj *b = new BigObj;
     //   presumably the point of fun() is that some initialisation
     //     of *b occurs here
    return b;
}
int main()
{
    BigObj *pb = fun();
}

并消除对BigObj 的潜在复制。唯一被复制的是指针的值。编译器因此不依赖 C++11 移动构造函数的存在来优化上述内容,因为它避免了不必要的对象创建和复制,因此这满足了 OP 不需要使用 C++11。

显然,在任何一种情况下,将 operator new 的使用与相应的 operator delete 相匹配都是一种很好的做法。

【讨论】:

    猜你喜欢
    • 2012-04-14
    • 2013-03-08
    • 2012-04-13
    • 2018-12-15
    • 2012-03-09
    • 2020-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多