【问题标题】:Class containing string: what really happens when it's returned from a function?包含字符串的类:从函数返回时真正发生了什么?
【发布时间】:2018-03-09 17:58:25
【问题描述】:

并不是我不信任我的编译器,而是我想知道发生了什么。假设我有

struct Foo {
  std::string s;
};

我想创建一个(在堆栈上),填写很长的字符串,然后从我的函数中返回它。

Foo f() {
  Foo foo {my_very_long_string};
  return foo;
  // OR: return Foo {my_very_long_string};
}

我知道有诸如 RVO 和移动语义之类的东西;我怎么知道它们正在被使用,并且在运行时它没有在堆上分配带有数据的新字符串,复制它并释放旧字符串? (除了我的程序会变慢。)

是否使用移动构造函数来重用字符串数据?还是使用 RVO 来实际返回相同的字符串?

【问题讨论】:

  • 如果您不信任该程序集,请编译并检查它。在你做类似的事情之前,尽管对代码进行基准测试。如果代码运行良好,就没有理由这样做。
  • “我怎么知道......在运行时它没有分配一个新的字符串......,复制它,然后释放旧的?”你可以保证这不会发生。如果没有 RVO,字符串将被移动而不是复制,因此不会有额外的分配
  • 这是一个相当哲学的问题。您如何确定其他人(在这种情况下是标准开发人员)告诉您的任何事情?我想你必须在某个时候相信他们。或者自己挖掘一切,但你知道,生命是短暂的。 :)
  • 在一种情况下你必须小心——如果你定义了一个析构函数(或复制 ctor 或复制分配),你不会得到一个为你定义的隐式移动 ctor。因此您的对象变得不可移动,因此会出现副本(包括原本可以移动的字段)
  • “我知道有诸如 RVO 和 move 语义之类的东西;我怎么知道它们正在被使用” (A) 阅读标准或它的适当摘要,了解这些优化可以发生的时间。 (B) 阅读您(完全未知的)编译器生成的程序集,看看是否发生了此类优化确实。

标签: c++ c++11 move rvo


【解决方案1】:

NRVO 或为命名对象移动

在函数中:

Foo f() {
  Foo foo{my_very_long_string};
  return foo;
}

对象foo 有一个名称(即:foo),它是一个命名的对象。

命名 RVO (NRVO),这是一种可选的优化,可能会发生。如果没有发生 NRVO,则移动 foo,因为它是本地对象,因此在此上下文中被视为右值(即:return 语句)。


RVO/copy elsion or move for unnamed objects

但是,在函数中:

Foo f() {
  return Foo{my_very_long_string};
}

关注一个未命名对象,它是由Foo{my_very_long_string}产生的对象。

  • 从 C++17 开始,副本必须省略(即:效果与 RVO 相同,但语义不同)。

  • 在 C++17 之前,可能会出现 RVO,它当时是一种可选的优化。如果不是,则移动它,因为Foo{my_very_long_string} 已经是一个右值。


在上述任何情况下都不会为新字符串分配堆。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-24
    • 2020-01-08
    • 1970-01-01
    • 2013-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多