什么时候在C ++ 11中的const引用比传递值更好?

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

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

我听说有了新的C ++ 11function,您可以按如下方式通过值传递vector ,而不会影响性能。

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

例如, 这个答案说

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

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

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

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

通过价值传递的一般规则是什么时候你最终会复制一份拷贝 。 这就是说,而不是这样做:

 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引用传递仍然是首选,有效的方法。

有一个很大的不同。 除非它死了,否则你会得到一个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和右值引用的引入改变了有关像向量一样返回对象的规则 – 现在你可以做到这一点 (不必担心保证副本)。 没有关于它们作为参数的基本规则改变了 – 但是你仍然应该通过const引用来引用它们,除非你真的需要一个真正的拷贝 – 那么就是按照值来做。

请记住,如果您没有传入r值,那么按值传递将导致完整的副本。 所以一般来说,通过价值传递可能会导致业绩下滑。

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

你给的样本是价值传递的样本

 int hd(vector<int> a) { 

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

即使你已经正确地宣布'高清'取右值

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

它可能比通过价值更便宜,但执行一个成功的举动 (而不是一个简单的std::move可能根本没有任何作用)可能比简单的通过引用更昂贵。 一个新的vector<int>必须被构造,并且必须拥有a的内容。 我们没有旧的开销,不得不分配一个新的数组元素,并复制值,但我们仍然需要传输vector的数据字段。

更重要的是,在成功的情况下,在这个过程中会被摧毁:

 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. } 

作为基本规则:

  • 传递微不足道的东西,
  • 如果目的地需要可变的副本,则按值传递,
  • 如果你总是需要复印,
  • 通过常量引用传递非重要对象,其中观察者只需要看到内容/状态,但不需要它是可修改的,
  • 当目标需要临时/构造值的可变副本时std::move(std::string("a") + std::string("b"))) (例如std::move(std::string("a") + std::string("b")))
  • 当您需要对象状态的位置但想要保留现有的值/数据并释放当前的持有者时移动

你的例子是有缺陷的。 C + + 11不会给你一个代码,你会得到一个副本。

但是,你可以通过声明该函数采取一个右值引用,然后传递一个:

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

这是假设你不会在你的函数中再次使用variablesa ,除非摧毁它或者为它赋值一个新的值。 在这里, std::move将该值转换为右值引用,允许移动。

const引用允许临时创build静态。 您可以传入适合隐式构造函数的内容,并创build一个临时对象。 经典的例子是一个char数组被转换为const std::string&但是使用std::vectorstd::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});