提问人:thor 提问时间:7/3/2014 最后编辑:Communitythor 更新时间:3/25/2017 访问量:20724
在 C++11 中,常量引用何时优于按值传递?
When is a const reference better than pass-by-value in C++11?
问:
我有一些 C++ 之前的 11 代码,其中我使用引用来传递像 ' 很多这样的大参数。示例如下:const
vector
int hd(const vector<int>& a) {
return a[0];
}
我听说使用新的 C++11 功能,您可以按如下方式传递 by 值,而不会影响性能。vector
int hd(vector<int> a) {
return a[0];
}
例如,这个答案说
C++ 11 的移动语义使得按值传递和返回更具吸引力,即使对于复杂的对象也是如此。
上述两个选项在性能方面是相同的,这是真的吗?
如果是这样,什么时候使用选项 1 中的常量引用比选项 2 更好?(即为什么我们仍然需要在 C++11 中使用 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 和右值引用的引入改变了返回对象(如向量)的规则 - 现在您可以这样做(无需担心保证副本)。但是,没有改变将它们作为参数的基本规则 - 除非您实际上需要真正的副本,否则您仍然应该通过常量引用来获取它们 - 然后按值获取。
评论
move
return std::move(something)
std::vector<T> v; v.reserve(count + 2); v.emplace_back(x); v.emplace_back(y); /*...*/ foo(std::move(v));
请记住,如果不传入 r 值,则按值传入将导致完整的副本。因此,一般来说,按值传递可能会导致性能下降。
按值传递的一般经验法则是,无论如何,您最终都会制作副本。也就是说,与其这样做,不如这样做:
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 中部分正确,并且在移动语义中变得更加有用,因为当使用右值调用函数时,复制可能会被逐个传递情况下的移动所取代。
否则,当您只想读取数据时,按引用传递仍然是首选的有效方法。const
评论
const
std::vector<int> &&x
const int& x
int x
你的例子是有缺陷的。C++ 11 不会使用您拥有的代码移动,并且会进行复制。
但是,您可以通过声明函数以接受右值引用,然后传递一个来获得移动:
int hd(vector<int>&& a) {
return a[0];
}
// ...
std::vector<int> a = ...
int x = hd(std::move(a));
这是假设您不会再次在函数中使用该变量,除非销毁它或为其分配一个新值。在这里,将值强制转换为右值引用,从而允许移动。a
std::move
常量引用允许以静默方式创建临时对象。您可以传入适合隐式构造函数的内容,并且将创建一个临时构造函数。典型的例子是将 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});
评论
int hd(vector<int>&& a);
a
a
std::move(a)
hd()
C++ 11 的移动语义使得按值传递和返回更具吸引力,即使对于复杂的对象也是如此。
但是,您给出的样本是按值传递的样本
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 %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::move()
评论
const