36 / 80 · C++14 · 约 9 分钟
参数传递 Parameter passing 与所有权契约
选参数类型时先决定函数只是读取、需要修改,还是接收所有权,再比较复制成本。小值按值,大型只读对象用 const 引用,可空借用用指针,独占所有权按值传 unique_ptr;这些类型传达的是不同契约。
传值不是一律低效
按值参数是独立的参数对象,修改它通常不会修改调用者的那个对象。整数和轻量句柄按值传递简单清晰,是否更快需要结合类型与目标平台。按 const 引用传大型只读对象可以避免复制,但增加别名关系,并要求调用期间目标一直有效。
复制一个指针只复制指针值:函数可以通过副本修改同一个目标,却不能靠给这个副本重新赋地址来替换调用者的指针。若确实要重设外部指针,可以传指针引用;更多时候直接返回新结果,接口更容易理解。
借用与接管要在签名里区分
T& 适合必需的可修改借用,const T& 适合必需的只读借用,T* 可表达可选目标。它们都不应默认为转移所有权。函数如果需要销毁或长期保存独占资源,应按值接收 std::unique_ptr<T>,调用方明确使用 move 交接责任。
只读取 T 的函数没有必要接收 shared_ptr。把智能指针参数留给需要操作所有权的接口,可以避免不必要的引用计数更新,也让使用自动对象或 unique_ptr 的调用者无需迁就某种存储策略。
保留数据时选择拥有型表示
示例中的 decorate 按值接收字符串,因为它需要本地可修改副本,并按值返回结果;调用者的原字符串不变。consume 接收 unique_ptr,返回整数后由参数析构回收资源。调用前后的断言明确显示独占所有权已被转移。
需要把文本保存到对象中时,按值接收 string 再移动进成员是常见做法;传入左值会复制,传入临时值可以移动。string_view 与 span 的按值传递只复制视图,不复制底层数据,不能因此把它们直接保存到超过输入生存期的异步任务中。
容易答错的地方
std::move只是允许选择移动操作的转换,不保证任意类型都执行移动,也不会单独释放资源。- const 引用参数不能自动使函数保存的引用长期有效;调用结束后继续借用必须另有生存期保证。
运行一个例子
最低标准 C++14 · 完整程序 · 下载 .cpp
#include <cassert>
#include <memory>
#include <string>
#include <utility>
std::string decorate(std::string text) {
text += '!';
return text;
}
int consume(std::unique_ptr<int> value) {
assert(value);
return *value;
}
int main() {
std::string original = "C++";
assert(decorate(original) == "C++!");
assert(original == "C++");
auto owner = std::make_unique<int>(42);
int result = consume(std::move(owner));
assert(result == 42);
assert(!owner);
}
在本地编译
g++ -std=c++14 -Wall -Wextra -Wpedantic -pthread memory-parameters.cpp -o example && ./example预期结果
预期:正常退出、无输出;所有 assert 通过。
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
设计一个只在调用期间读取 Record、允许没有 Record 的函数参数;它需要 shared_ptr 吗?
查看参考答案
可以声明为 void inspect(const Record* record),在函数内先检查 record 是否为空,再只读访问。不需要 shared_ptr,因为没有保存或共享所有权。若目标必需,改成 const Record&;若要排入稍后执行的任务,则应改成拥有值副本,或明确按值接收 shared_ptr 以延长生存期。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。