什么时候在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::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});