详细说明c++函数传参常量引用const T传递和值传递的区别
在 C++ 中,是否使用 const T&
(常量引用) 作为函数参数取决于数据类型的大小、复制成本以及使用场景。以下是针对不同数据类型的详细分析:
1.基本原则
- 优先用
const T&
- 适用于:所有非平凡(non-trivial)拷贝的类型(如类对象、容器、字符串等)。
- 目的:避免拷贝开销,同时防止意外修改。
- 直接传值
- 适用于:内置类型(
int
/float
等) 或 小型且拷贝成本低的类型(如std::pair<int,int>
)。 - 原因:拷贝成本可能低于间接访问(引用本质是指针解引用)。
- 适用于:内置类型(
- 特殊情况
- 需要修改参数时:用 非
const
引用(T&
) 或 传值 + 移动语义。
- 需要修改参数时:用 非
2.具体数据类型分析
2.1 内置类型(int
, double
, char
等)
-
推荐直接传值(除非需要修改原对象):
void printInt(int val); // 直接传值 void increment(int& val); // 需要修改时用非const引用
- 原因:拷贝成本极低(通常一个 CPU 周期),传引用反而可能因指针解引用降低性能。
2.2 STL 容器(std::vector
, std::map
, std::string
等)
-
优先用
const T&
:void processVector(const std::vector<int>& vec); void analyzeString(const std::string& str);
- 原因:容器可能包含大量数据,拷贝成本高(如
std::vector
需要分配新内存并复制所有元素)。
- 原因:容器可能包含大量数据,拷贝成本高(如
2.3 自定义类对象
-
规则与 STL 容器相同:
class MyClass { /* ... */ }; void useObject(const MyClass& obj); // 推荐
- 例外:如果类是 小型且拷贝成本低(如仅包含几个
int
的 POD 类型),可考虑传值。
- 例外:如果类是 小型且拷贝成本低(如仅包含几个
2.4 指针类型(T\*
)
-
语义差异:
-
传指针本身是传值(指针的拷贝成本低),但需注意是否用const修饰指向的数据:
void readData(const Data* ptr); // 不修改指向的数据 void modifyData(Data* ptr); // 需要修改数据
-
如果目的是避免拷贝,优先用
const T&
而非const T*
(更安全,无需检查nullptr
)。
-
2.5 数组类型
-
C 风格数组:
-
实际传递的是指针,需显式传递大小或使用std::span(C++20):
void processArray(const int* arr, size_t size); // C风格 void processArray(std::span<const int> arr); // C++20推荐
-
-
std::array:
- 与 STL 容器相同,优先用 const std::array<T,N>&。
2.6 函数对象(Lambda/函数指针)
-
Lambda 和 std::function:
-
如果不需要存储,用const T&;需要存储时用传值 + 移动语义:
void callLambda(const std::function<void()>& func); void storeLambda(std::function<void()> func); // 传值 + std::move
-
-
函数指针:
-
直接传值(拷贝成本低):
void registerCallback(void (*func)(int));
-
2.7 智能指针(std::shared_ptr
, std::unique_ptr
)
- 语义差异:
- 需要共享所有权时:传
std::shared_ptr<const T>
(避免误修改数据)。 - 需要转移所有权时:传
std::unique_ptr<T>
按值(移动语义)。 - 仅观察不拥有时:传
const T&
或原始指针(需明确生命周期)。
- 需要共享所有权时:传
3.例外情况
3.1 需要修改参数时
-
非
const
引用(T&
):void updateConfig(Config& cfg); // 需要修改原对象
-
移动语义(
T&&
或传值 +std::move
):void takeOwnership(std::string str); // 传值后移动 void takeOwnership(std::string&& str); // 直接右值引用
3.2 小型且拷贝成本低的类型
-
例如std::pair<int,int>、std::array<int, 3>等:
void usePoint(std::pair<int,int> pt); // 直接传值
3.3 模板泛型编程
-
通用引用(T&&)配合std::forward:
template <typename T> void forwardExample(T&& arg) { use(std::forward<T>(arg)); // 完美转发 }
4.总结表格
数据类型 | 推荐传递方式 | 原因 |
---|---|---|
内置类型(int /float ) | 直接传值 | 拷贝成本低,传引用可能降低性能 |
STL 容器/std::string | const T& | 避免深拷贝开销 |
自定义类对象 | const T& (除非小型且拷贝成本低) | 平衡安全性和性能 |
指针类型 | const T* 或 T* | 明确是否修改指向的数据 |
智能指针 | 按语义选择(见上文) | 区分所有权和观察者角色 |
函数对象 | const std::function& 或传值 | 取决于是否需要存储 |
小型结构体(如 Point ) | 直接传值 | 拷贝成本可能低于间接访问 |
5.最终建议
- 默认优先用
const T&
,除非类型是内置或极小型的 POD。 - 需要修改参数时:
- 用
T&
(修改原对象)或传值 + 移动语义(需要内部副本)。
- 用
- 模板泛型:考虑通用引用(
T&&
)实现完美转发。 - 始终明确参数意图:通过
const
和非const
区分只读和可修改参数。