在C++程序开发中,函数调用是最基础的操作之一,而参数传递和返回值的处理方式会直接影响函数调用的性能开销,不合理的设计可能导致大量不必要的拷贝操作,降低程序运行效率。

参数传递方式对性能的影响
值传递的开销
值传递会将实参的完整拷贝传递给形参,对于基础数据类型来说开销很小,但对于自定义类类型或者大尺寸数据结构,会触发拷贝构造函数,带来较大的性能消耗。以下是值传递的示例代码:
#include <iostream>
#include <vector>
// 值传递函数,会触发vector的拷贝构造
void process_vector_by_value(std::vector<int> vec) {
// 处理vector的逻辑
vec.push_back(10);
}
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
// 调用时会发生vector的深拷贝
process_vector_by_value(data);
return 0;
}引用传递和指针传递的优势
引用传递和指针传递仅传递地址,不会触发对象的拷贝构造,适合传递大尺寸对象。引用传递语法更简洁,指针传递可以传递空值,二者在性能上几乎没有差异。示例代码如下:
#include <iostream>
#include <vector>
// 常量左值引用传递,避免拷贝且防止函数内修改原对象
void process_vector_by_const_ref(const std::vector<int>& vec) {
// 只读访问vec的内容
for (int num : vec) {
std::cout << num << " ";
}
}
// 指针传递,可传递空值
void process_vector_by_pointer(const std::vector<int>* vec_ptr) {
if (vec_ptr == nullptr) {
return;
}
for (int num : *vec_ptr) {
std::cout << num << " ";
}
}
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
process_vector_by_const_ref(data);
process_vector_by_pointer(&data);
return 0;
}右值引用传递的场景
当函数需要接管参数的所有权时,可以使用右值引用传递,配合移动语义避免不必要的拷贝。比如函数需要存储传入的对象时,右值引用可以触发移动构造,比值传递效率更高。
#include <iostream>
#include <vector>
#include <string>
class DataContainer {
private:
std::vector<std::string> data;
public:
// 右值引用参数,触发移动构造
void set_data(std::vector<std::string>&& new_data) {
data = std::move(new_data);
}
};
int main() {
std::vector<std::string> temp = {"a", "b", "c"};
DataContainer container;
// 传递右值,触发移动赋值
container.set_data(std::move(temp));
return 0;
}返回值对性能的影响
返回值优化(RVO/NRVO)
编译器默认会开启返回值优化,当函数返回一个临时对象时,编译器会直接在调用方的内存空间构造该对象,避免拷贝操作。下面的代码在没有返回值优化的情况下会发生两次拷贝,开启优化后没有拷贝开销:
#include <iostream>
class Test {
public:
Test() { std::cout << "构造" << std::endl; }
Test(const Test&) { std::cout << "拷贝构造" << std::endl; }
Test(Test&&) { std::cout << "移动构造" << std::endl; }
};
// 返回临时对象,会触发返回值优化
Test create_test() {
return Test();
}
int main() {
Test t = create_test();
return 0;
}返回引用和指针的注意事项
返回引用或指针可以避免返回值的拷贝,但必须确保返回的对象生命周期长于函数调用,不能返回局部变量的引用或指针,否则会导致悬垂引用或野指针问题。示例代码如下:
#include <iostream>
#include <vector>
// 正确:返回全局变量的引用
std::vector<int> global_vec = {1,2,3};
const std::vector<int>& get_global_vec() {
return global_vec;
}
// 错误:返回局部变量的引用,未定义行为
const std::vector<int>& get_local_vec_error() {
std::vector<int> local = {4,5,6};
return local;
}
int main() {
const std::vector<int>& ref = get_global_vec();
// 错误示例不要调用,仅作演示
// const std::vector<int>& err_ref = get_local_vec_error();
return 0;
}移动语义在返回值中的应用
当返回值优化无法生效时,移动语义可以将返回值的资源转移给接收方,避免深拷贝。比如返回函数内动态创建的大对象时,移动返回比拷贝返回效率高很多。
#include <iostream>
#include <vector>
class BigData {
private:
std::vector<int> content;
public:
BigData(int size) : content(size, 0) {}
// 移动构造函数
BigData(BigData&& other) noexcept : content(std::move(other.content)) {
std::cout << "移动构造BigData" << std::endl;
}
// 拷贝构造函数
BigData(const BigData& other) : content(other.content) {
std::cout << "拷贝构造BigData" << std::endl;
}
};
BigData create_big_data() {
BigData temp(10000);
// 返回时会触发移动构造(如果返回值优化未生效)
return temp;
}
int main() {
BigData data = create_big_data();
return 0;
}性能调优实践建议
- 对于小尺寸基础类型,值传递性能开销可以忽略,优先使用值传递保证代码简洁
- 传递大尺寸自定义类型或者不需要修改的入参时,优先使用
const 类型&的常量左值引用传递 - 函数需要接管参数所有权时,使用右值引用传递配合
std::move转移资源 - 返回值优先依赖编译器的返回值优化,避免不必要的拷贝,不要为了优化手动返回指针或引用
- 返回值优化无法生效的场景下,确保自定义类型实现了正确的移动构造和移动赋值函数
性能对比总结
不同参数传递和返回值方式的性能对比如下表所示:
| 传递/返回方式 | 适用场景 | 性能开销 |
|---|---|---|
| 值传递(基础类型) | int、double等小尺寸类型 | 极低,可忽略 |
| 值传递(大对象) | 无,不推荐 | 高,触发深拷贝 |
| 常量左值引用传递 | 只读的大对象入参 | 极低,仅传递地址 |
| 右值引用传递 | 需要接管所有权的参数 | 低,配合移动语义转移资源 |
| 返回值优化 | 返回临时对象 | 无拷贝开销 |
| 移动语义返回 | 返回值优化未生效场景 | 低,资源转移无深拷贝 |