什么是const引用比C ++ 11中的pass-by-value更好?

时间:2014-07-03 00:48:25

标签: c++ c++11

我有一些pre-C ++ 11代码,其中我使用const引用来传递大量参数,例如vector&#39}。一个例子如下:

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

我听说使用新的C ++ 11功能,您可以按照以下方式传递vector,而不会影响性能。

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

例如,this answer

  

C ++ 11的移动语义使得传递和返回值更具吸引力,即使对于复杂的对象也是如此。

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

如果是这样,何时使用const引用,如选项1中的选项2更好? (即为什么我们仍然需要在C ++ 11中使用const引用)。

我问的一个原因是const引用使模板参数的推导变得复杂,并且如果它与const引用性能相同,那么仅使用pass-by-value会容易得多。

5 个答案:

答案 0 :(得分:69)

传递值的一般经验法则是,无论如何最终都要制作副本。也就是说,而不是这样做:

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中已经部分正确,并且随着移动语义变得更加有用,因为复制可能被替换为在pass-by-val情况下的移动用右值调用。

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

答案 1 :(得分:11)

有很大的不同。除非它即将死亡,否则你将获得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和rvalue引用的引入改变了关于返回像vector这样的对象的规则 - 现在你可以做到(不用担心保证副本)。虽然没有关于作为参数的基本规则改变了 - 你仍然应该通过const引用来接受它们,除非你真的需要一个真正的副本 - 然后按值获取。

答案 2 :(得分:7)

请记住,如果您没有传入r值,那么按值传递将导致完整的复制。所以一般来说,按值传递可能会导致性能下降。

答案 3 :(得分:7)

  

C ++ 11的移动语义使得传递和返回值更具吸引力,即使对于复杂的对象也是如此。

但是,您提供的样本是按值传递的样本

int hd(vector<int> a) {

所以C ++ 11对此没有影响。

即使您已正确宣布&#39; hd&#39;采取右值

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 %p\n", m_ptr)
        free(m_ptr);
        m_ptr = nullptr;
      }
    }
};

void hd(Str&& str) {
  printf("str.m_ptr = %p\n", 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 = %p\n", b.m_ptr); // it's been moved.
}

作为一般规则:

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

答案 4 :(得分:3)

你的榜样有缺陷。 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});