每周技巧 #55:名称计数与 unique_ptr
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #55: Name Counting and unique_ptr。
原文最初作为 totw/55 发布于 2013 年 9 月 12 日。
更新于 2017 年 10 月 20 日。
“虽然我们可以用一千个名字认识他,但对我们所有人来说,他始终是同一个。”– Mahatma Gandhi
通俗地说,一个值的“名字”是指任何作用域中持有某个特定数据值的值类型变量(不是指针,也不是引用)。(对规范律师来说,如果我们说“名字”,本质上是在谈 lvalue。)由于 std::unique_ptr 的特定行为要求,我们需要确保 std::unique_ptr 中持有的任何值都只有一个名字。
值得注意的是,C++ 语言委员会给 std::unique_ptr 选了一个非常贴切的名字。存储在 std::unique_ptr 中的任何非空指针值,在任意时刻都必须只出现在一个 std::unique_ptr 中;标准库就是按这个目标设计来强制执行的。很多使用 std::unique_ptr 的代码编译问题,都可以通过学会识别如何给 std::unique_ptr 计数名字来解决:一个名字没问题,但同一个指针值有多个名字就不行。
我们来数几个名字。在每一行编号处,数一数此时还活着的、引用同一个指针的 std::unique_ptr 名字有多少个(无论是否在作用域内)。如果你发现某一行里同一个指针值有超过一个名字,那就是错误!
|
|
在 Simple() 中,通过 NewFoo() 分配的 unique pointer 始终只有一个你可以用来引用它的名字:AcceptFoo() 内部的名字 “f”。
和它对比的是 DoesNotBuild():通过 NewFoo() 分配的 unique pointer 有两个名字引用它:DoesNotBuild() 中的 “g” 和 AcceptFoo() 中的 “f”。
这是典型的唯一性违规:在执行过程中的任意给定时刻,std::unique_ptr 持有的任何值(更一般地说,任何只可移动类型持有的值)只能被一个不同的名字引用。任何看起来像会引入额外名字的拷贝都是被禁止的,并且无法编译:
|
|
即使编译器没有抓住你,std::unique_ptr 的运行时行为也会抓住你。任何时候,只要你“聪明过了编译器”(见 SmarterThanTheCompilerButNot())并引入多个 std::unique_ptr 名字,它也许能编译(暂时),但你会遇到运行时内存问题。
现在问题变成:我们如何移除一个名字?C++11 也提供了解法,就是 std::move()。
|
|
对 std::move() 的调用实际上是一个名字擦除器:从概念上讲,你可以不再把 “h” 计为这个指针值的名字。现在它通过了不同名字规则:通过 NewFoo() 分配的 unique pointer 有一个名字(“h”),而在调用 AcceptFoo() 期间也再次只有一个名字(“f”)。通过使用 std::move(),我们承诺在给 “h” 重新赋新值之前,不会再读取它。
名称计数是现代 C++ 里的一个实用技巧,尤其适合那些还不熟悉 lvalue、rvalue 等细节的人:它可以帮助你识别不必要拷贝的可能性,也会帮助你正确使用 std::unique_ptr。计数之后,如果你发现某个点名字太多,就用 std::move 擦掉那个不再需要的名字。