参数传递在程序中非常普遍,数量也非常多。有时候看似一点点开销,但是积少成多。下面是不同情况下传递参数的一些总结。
普通参数
class Widget { ... };
void fun(const Widget* const widget); // 可以为nullptr的只读参数,第一个const表示widget本身不会被修改,第二表示widget这个指针只读
void fun(const Widget* const widget); // 可以为nullptr,可以被修改
void fun(Widget& widget); // 不为nullptr,可被修改
void fun(const Widget& widget); // 不为nullptr,只读
希望保存传入的参数
class Test {
public:
void fun(Widget widget) { // fun收到了左值,会有一次拷贝构造,一次移动赋值;如果fun收到了右值,会有一次移动构造,一次移动赋值;综合,这样写效率最高。
_widget = std::move(widget);
}
private:
Widget _widget;
};
unique_ptr
void fun(std::unique_ptr<T> obj); // 表明fun方法希望获取T的所有权
如果不需要传递所有权,应该按照普通数据结构来传递,不要直接传递unique_ptr
auto obj = std::make_unique<std::string>();
// void fun(const std::string* const str); // 读string
// void fun(std::string* const str); // 写string
fun(obj.get());
// void fun(const std::string& str); // 读string
// void fun(std::string& str); // 写string
fun(*obj);
在工厂模式场景,可以传递unique_ptr
void factory(std::unique_ptr<std::string>& obj) {
obj = std::make_unique<std::string>();
}
shared_ptr
void fun(std::shared_ptr<T> obj); // fun希望获取obj的所有权,会导致shared_ptr的引用计数加一,他是原子操作,是一个低效的操作
如果不需要分享所有权,应该按照普通数据结构来传递,不要直接传递shared_ptr
Lambda 表达式
lambda的底层实现
捕获值
int x = 0;
auto func = [=](int i)->bool { return i > x; };
// 编译器实现
struct lambda_xyz { // 编译器自动生成一个唯一的名字
lambda_xyz(int x) : _x(x) {} // 捕获值时,构造lambda_xyz时以值传递
bool operator()(int i) const { return i > _x; }
int _x;
};
捕获引用
int x = 0;
auto func = [&](int i)->bool { return i > x; };
// 编译器实现
struct lambda_xyz { // 编译器自动生成一个唯一的名字
lambda_xyz(int& x) : _x(x) {} // 捕获引用时,构造lambda_xyz时以引用传递
bool operator()(int i) const { return i > _x; }
int& _x;
};
以值传递方式传递lambda表达式
void bar(std::function<bool(int)> func) { // 由于function是类,所以会调用他的拷贝构造函数
bool res = func(100);
}
void bar(const std::function<bool(int)>& func) { // 如果std::function提前已经构造好了,const引用方式效率会更高一些
bool res = func(100);
}
void run() {
int x = 0;
auto func = [=](int i)->bool { return i > x; };
bar(func);
}
用std::function传递lambda表达式的优缺点:
- 优点:有明确的参数类型和返回值类型,编译器会强制检查
- 缺点:有内存开销,对特别小的lambda不友好
- 建议:在性能要求不高的地方可以使用
在高性能场景怎么传递呢?
- 模板方式
template <typename T>
void bar(T func) {
bool res = func(5);
}
int x = 0;
auto func = [=](int i)->bool { return i > x; };
bar(func); // 调用lambda的拷贝构造
bar(std::move(func)); // 调用lambda的移动构造
模板方式的优缺点
- 优点:可以适应各种情况
- 缺点:失去了强制类型检查
- 完美转发
template <typename T>
void bar(T&& func) {
bool res = func(5);
}
int x = 0;
auto func = [=](int i)->bool { return i > x; };
bar(func); // 左值引用的方式
bar(std::move(func)); // 右值引用方式
完美转发方式的优缺点
- 优点:没有内存分配的开销,也没有额外的拷贝
- 缺点:失去了强制类型检查,另外都是引用方式传递
传递initializer_list
例子
void fun(std::initializer_list<int> array);
fun({1, 2, 3});
initializer_list不需要const initializer_list&, 因为:
- initializer_list非常小,引用和传值相当
- initializer_list底层是const T[N],本身是const的
但是写成const initializer_list&看着舒服,也没有什么额外开销 😃