在c++ 11中,When是比按值传递更好的const引用

When is a const reference better than pass-by-value in C++11?

本文关键字:更好 按值传递 const 引用 c++ When      更新时间:2023-10-16

我有一些c++ 11之前的代码,其中我使用const引用来传递像vector这样的大参数。示例如下:

int hd(const vector<int>& a) {
   return a[0];
}

我听说在c++ 11的新特性中,您可以按以下值传递vector而不会影响性能。

int hd(vector<int> a) {
   return a[0];
}

例如,这个答案说

c++ 11的move语义使得传递和按值返回即使对于复杂的对象也更有吸引力。

以上两个选项是否在性能方面是相同的?

如果是,什么时候使用const引用选项1比选项2好?(例如,为什么c++ 11中仍然需要使用const引用)。

我问这个问题的一个原因是const引用使模板形参的推导变得复杂,如果在性能方面与const引用相同,那么只使用按值传递会容易得多。

按值传递的一般经验规则是当您最终将生成副本时。也就是说,与其这样做:

void f(const std::vector<int>& x) {
    std::vector<int> y(x);
    // stuff
}

如果你先传递一个const-ref和,然后复制它,你应该这样做:

void f(std::vector<int> x) {
    // work with x instead
}

这在c++ 03中部分是正确的,并且在move语义中变得更加有用,因为当用右值调用函数时,复制可以用move代替

否则,当您想要做的只是读取数据时,通过const引用传递仍然是首选的,有效的方法。

差别很大。您将获得vector内部数组的副本,除非它即将死亡。

int hd(vector<int> a) {
   //...
}
hd(func_returning_vector()); // internal array is "stolen" (move constructor is called)
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
hd(v); // internal array is copied (copy constructor is called)

c++ 11和右值引用的引入改变了关于返回对象(如vector)的规则——现在可以这样做(而不用担心保证复制)。关于作为参数的基本规则没有改变——你仍然应该通过const引用接受它们,除非你真的需要一个真正的按值复制。

c++ 11的move语义使得传递和按值返回即使对于复杂的对象也更有吸引力。

然而,你给出的样本是一个通过值

的样本
int hd(vector<int> a) {

所以c++ 11对这个没有影响。

即使你正确地声明了'hd'接受右值

int hd(vector<int>&& a) {

可能比按值传递便宜,但执行成功移动(与简单的std::move相反,可能根本没有效果)可能比简单的按引用传递更昂贵。必须构造一个新的vector<int>,并且它必须拥有a的内容。我们不再需要像以前那样分配一个新的元素数组并复制其值,但是我们仍然需要传输vector的数据字段。

更重要的是,在成功移动的情况下,a将在此过程中被破坏:

std::vector<int> x;
x.push(1);
int n = hd(std::move(x));
std::cout << x.size() << 'n'; // not what it used to be

考虑以下完整的例子:

struct Str {
    char* m_ptr;
    Str() : m_ptr(nullptr) {}
    Str(const char* ptr) : m_ptr(strdup(ptr)) {}
    Str(const Str& rhs) : m_ptr(strdup(rhs.m_ptr)) {}
    Str(Str&& rhs) {
      if (&rhs != this) {
        m_ptr = rhs.m_ptr;
        rhs.m_ptr = nullptr;
      }
    }
    ~Str() {
      if (m_ptr) {
        printf("dtor: freeing %pn", m_ptr)
        free(m_ptr);
        m_ptr = nullptr;
      }
    }
};
void hd(Str&& str) {
  printf("str.m_ptr = %pn", str.m_ptr);
}
int main() {
  Str a("hello world"); // duplicates 'hello world'.
  Str b(a); // creates another copy
  hd(std::move(b)); // transfers authority for b to function hd.
  //hd(b); // compile error
  printf("after hd, b.m_ptr = %pn", b.m_ptr); // it's been moved.
}

一般规则:

  • 按值传递琐碎对象,
  • 如果目标需要一个可变副本,则按值传递,
  • 按值传递如果总是需要复制,
  • 通过const引用传递非平凡对象,查看者只需要查看内容/状态,但不需要修改,
  • 当目标需要临时/构造值的可变副本时移动(例如std::move(std::string("a") + std::string("b"))))。
  • 当您需要对象状态的局域性,但希望保留现有值/数据并释放当前持有人时移动。

请记住,如果不传递r值,则按值传递将导致完整的副本。所以一般来说,按值传递可能会导致性能下降。

你的例子有缺陷。c++ 11不允许你对已有的代码进行移动,并且会创建一个副本。

但是,您可以通过声明函数接受右值引用,然后传递一个来移动:

int hd(vector<int>&& a) {
   return a[0];
}
// ...
std::vector<int> a = ...
int x = hd(std::move(a));

这是假设您不会在函数中再次使用变量a,除非销毁它或为它分配一个新值。这里,std::move将值强制转换为右值引用,允许移动。

Const引用允许静默创建临时对象。您可以传入一些适合隐式构造函数的内容,然后创建一个临时构造函数。典型的例子是将一个char数组转换为const std::string&,但使用std::vector,可以转换为std::initializer_list

:

int hd(const std::vector<int>&); // Declaration of const reference function
int x = hd({1,2,3,4});

当然,你也可以把临时文件移进来:

int hd(std::vector<int>&&); // Declaration of rvalue reference function
int x = hd({1,2,3,4});