c++ - C++ 编译器是否优化按值返回成员变量

标签 c++ return return-value

当返回的变量超出函数的范围时,我非常了解 C++ 返回值优化,但是返回成员变量呢?考虑以下代码:

#include <iostream>
#include <string>

class NamedObject {
 public:
  NamedObject(const char* name) : _name(name) {}
  std::string name() const {return _name;}

 private:
  std::string _name;
};

int main(int argc, char** argv) {
  NamedObject obj("name");
  std::cout << "name length before clear: " << obj.name().length() << std::endl;
  obj.name().clear();
  std::cout << "name length after clear: " << obj.name().length() << std::endl;
  return 0;
}

哪些输出:

name length before clear: 4
name length after clear: 4

显然,obj.name().clear() 作用于临时拷贝,但是对 obj.name.length() 的调用又如何呢? std::string::length()const 成员函数,因此保证不会修改字符串的状态。那么,应该允许编译器不复制成员变量而直接将其用于对 const 成员函数的调用似乎是合理的。现代 C++ 编译器是否进行了这种优化?有什么理由不应该或不能制作它吗?

编辑:

澄清一下,我不是在问标准返回值优化是否在这里起作用;我明白为什么在我最初问这个问题时没有。通常定义的 RVO 在这里不起作用,因为返回的值没有超出函数的范围。

我要问的是:如果调用时的编译器可以确定调用没有副作用,是否允许跳过复制?即,它可以表现得好像

obj.name().length()

obj._name.length()

最佳答案

name() 函数按值返回,这意味着所有操作都在临时对象上执行。

It seems reasonable, then, that compilers should be allowed to not copy the member variable and just use it directly for calls to const member functions.

这个假设在很多方面都是不正确的。当函数声明为 const 时, 告诉编译器您不会修改对象的状态,以便编译器可以帮助您验证这一点。返回类型是编译器可以为您执行的检查的一部分。例如,如果您将返回类型更改为:

std::string& name() const { return _name; }

编译器会提示:您 promise name() 不会修改状态,但您提供了一个引用,其他人 可以通过该引用进行修改。此外,该函数的语义是它提供了调用者可以修改的拷贝。如果拷贝被省略(不可能省略它,但为了争论),那么调用代码可能会修改看起来像本地拷贝的内容,实际上会修改对象的状态。

一般来说,当提供常量访问器时,您应该向成员返回引用,而不是拷贝

I have a pretty good grasp on C++ return value optimizations for temporaries, [...] Do modern C++ compilers make this optimization? Is there any reason why it shouldn't or can't be made?

我感觉你对返回值优化是什么没有真正把握好,不然你也不会出第二题了。让我们举个例子。当用户代码有:

std::string foo() {
   std::string result;
   result = "Hi";
   return result;
}
std::string x = foo();

在上面的代码中,可能存在三个字符串:foo 中的 result、返回值(我们称它为 __ret)和 x,以及可以应用的两种可能的优化:NRVO 和通用复制省略NRVO 是编译器在处理函数foo 时进行的优化,它由mergint result__ret 组成通过将它们放在同一位置并创建单个对象。优化的第二部分必须在调用方完成,它再次合并两个对象 x__ret 的位置。

至于实际实现,我先从第二个说起。调用者(在大多数调用约定中)负责为返回的对象分配内存。如果没有优化(并且在一种伪代码中),这就是调用者发生的事情:

[uninitialized] std::string __ret;
foo( [hidden arg] &__ret );          // Initializes __ret
std::string x = __ret;

现在,因为编译器知道临时 __ret 只会初始化 x,所以它会将代码转换为:

[uninitialized] std::string x;
foo( [hidden arg] &x );             // Initializes x

并且省略了调用者的拷贝。 foo 中的拷贝以类似的方式被省略。转换后的(为了遵守调用约定)函数是:

void foo( [hidden uninitialized] std::string* __ret ) {
   std::string result;
   result = "Hi";
   new (__ret) std::string( result );   // placement new: construct in place
   return;
}

现在这种情况下的优化是完全一样的。由于 result 的存在只是为了能够初始化返回的对象,它可以重用相同的空间,而不是创建一个新对象:

void foo( [hidden uninitialized] std::string* __ret ) {
   new (__ret) std::string();
   (*__ret) = "Hi";
   return;
}

现在回到你原来的问题,因为成员变量在调用成员函数之前就已经存在,所以这个优化是不能应用的。编译器无法将返回值放在与成员属性相同的位置,因为该变量已经存在于已知位置,该位置不是 __ret(由调用者提供)的地址。

我写了关于 NRVOcopy elision在过去。您可能有兴趣阅读这些文章。

关于c++ - C++ 编译器是否优化按值返回成员变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11057836/

相关文章:

c# - 将 ADODB::Recordset^ 转换为 _RecordsetPtr

c++ - 如何使用前后值对 vector 进行变换

java - Getter 方法返回两个不同的对象identityHashCodes

javascript - AJAX 仅加载和返回有限数量的对象

c++ - 为什么 Windows API 提供 2 种获取 "return"值的方法?

variables - 变量的不同行为和函数的返回值

c++ - 使用公共(public)成员函数访问私有(private)成员变量时出错 : Variable "was not declared in this scope"

c++ - 如何处理QPainter的边界矩形单击

javascript - 将函数作为这样的函数的参数在语法上可以接受吗?

C exit 表现得像 return 吗?