对于可拷贝的形参,如果移动成本低且一定会被拷贝则考虑传值¶
一些函数的形参本身就是用于拷贝的,考虑性能,对左值实参应该执行拷贝,对右值实参应该执行移动:
class A {
public:
void f(const std::string& s) { v.push_back(s); }
void f(std::string&& s) { v.push_back(std::move(s)); }
private:
std::vector<std::string> v;
};
为同一个功能写两个函数太过麻烦,因此改用为参数为转发引用的模板:
class A {
public:
template<typename T>
void f(T&& s)
{
v.push_back(std::forward<T>(s));
}
private:
std::vector<std::string> v;
};
但模板会带来复杂性,一是模板一般要在头文件中实现,它可能在对象代码中产生多个函数,二是如果传入了不正确的实参类型,将出现十分冗长的错误信息,难以调试。所以最好的方法是,针对左值拷贝,针对右值移动,并且在源码和目标代码中只需要处理一个函数,还能避开转发引用,而这种方法就是按值传递:
class A {
public:
void f(std::string s) { v.push_back(std::move(s)); }
private:
std::vector<std::string> v;
};
C++98 中,按值传递一定是拷贝构造,但在 C++11 中,只在传入左值时拷贝,如果传入右值则移动:
A a;
std::string s("hi");
a.f(s); //@ 以传左值的方式调用
a.f("hi"); //@ 以传右值的方式调用
对比不同方法的开销,重载和模板的成本是,对左值一次拷贝,对右值一次移动(此外模板可以用转发实参直接构造,可能一次拷贝或移动都不要)。传值一定会对形参有一次拷贝(左值)或移动构造(右值),之后再移动进容器,因此对左值一次拷贝一次移动,对右值两次移动。对比之下,传值只多出了一次移动,虽然成本高一些,但极大避免了麻烦。
可拷贝的形参才考虑传值,因为 move-only 类型只需要一个处理右值类型的函数:
class A {
public:
void f(std::unique_ptr<std::string>&& q) { p = std::move(q); }
private:
std::unique_ptr<std::string> p;
};
如果使用传值,则同样的调用需要先移动构造形参,多出了一次移动:
class A {
public:
void f(std::unique_ptr<std::string> q) { p = std::move(q); }
private:
std::unique_ptr<std::string> p;
};
只有当移动成本低时,多出的一次移动才值得考虑,因此应该只对一定会被拷贝的形参传值:
class A {
public:
void f(std::string s)
{
if (s.length() <= 15)
{ //@ 不满足条件则不添加,但多出了一次析构,不如直接传引用
v.push_back(std::move(s));
}
}
private:
std::vector<std::string> v;
};
之前的函数通过构造拷贝,如果通过赋值来拷贝,按值传递可能存在其他额外开销,这取决于很多方面,比如传入类型是否使用动态分配内存、使用动态分配内存时赋值运算符的实现、赋值目标和源对象的内存大小、是否使用 SSO::
class A {
public:
explicit A(std::string x) : s(std::move(x)) {}
void f(std::string x) { s = std::move(x); }
private:
std::string s;
};
std::string s("hello");
A a(s);
std::string x("hi");
a.f(x); //@ 额外的分配和回收成本,可能远高于std::string的移动成本
//@ 传引用则不会有此成本,因为现在a.s的长度比之前小
std::string y("hello world");
a.f(y); //@ a.s比之前长,传值和传引用都有额外的分配和回收成本,开销区别不大
还有一个与效率无关的问题,不同于按引用传递,按值传递容易遇见切片问题(the slicing problem):
class A {};
class B : public A {};
void f(A); //@ 接受A或B
B b;
f(b); //@ 只能看到一个A而不是一个B,B的独有特征被切割掉