本周提示 #55:名称计数和 unique_ptr

原文发布于 2013-09-12,totw/55

作者:Titus Winters (titus@google.com)

更新于 2017-10-20

快速链接:abseil.io/tips/55

“虽然我们可能用千种名字称呼他,但在我们所有人眼中,他始终是一个。” —— 圣雄甘地

在口语中,值的“名字”是指任何具有特定数据值的值类型变量(不是指针,也不是引用),在任何作用域内。 (对于规格法学家来说,如果我们说“名字”,我们基本上是在讨论 lvalues。)由于 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 的名字数量,这些名字指向同一个指针。如果你发现任何一行有多个名字指向同一个指针值,那就是错误!

std::unique_ptr<Foo> NewFoo() {
  return std::unique_ptr<Foo>(new Foo(1));
}

void AcceptFoo(std::unique_ptr<Foo> f) { f->PrintDebugString(); }

void Simple() {
  AcceptFoo(NewFoo());
}

void DoesNotBuild() {
  std::unique_ptr<Foo> g = NewFoo();
  AcceptFoo(g); // DOES NOT COMPILE!
}

void SmarterThanTheCompilerButNot() {
  Foo* j = new Foo(2);
  // Compiles, BUT VIOLATES THE RULE and will double-delete at runtime.
  std::unique_ptr<Foo> k(j);
  std::unique_ptr<Foo> l(j);
}

Simple() 中,通过 NewFoo() 分配的唯一指针只有一个名字可以引用它:在 AcceptFoo() 中的名字 “f”。

对比 DoesNotBuild():通过 NewFoo() 分配的唯一指针有两个名字指向它:DoesNotBuild() 的 “g” 和 AcceptFoo() 的 “f”。

这是经典的唯一性违规:在任何给定的执行点,std::unique_ptr(或更一般地,任何只可移动类型)持有的任何值只能通过一个唯一的名字引用。任何看起来像是复制的行为引入了额外的名字都是禁止的,编译器不会允许:

scratch.cc: error: call to deleted constructor of std::unique_ptr<Foo>'
  AcceptFoo(g);

即使编译器没有捕捉到你,std::unique_ptr 的运行时行为也会发现问题。任何时候你“聪明”地超越编译器(见 SmarterThanTheCompilerButNot())并引入多个 std::unique_ptr 名字,它可能会编译(现在),但你会在运行时遇到内存问题。

现在的问题是:我们如何移除一个名字?C++11 为此提供了解决方案,即 std::move()

 void EraseTheName() {
   std::unique_ptr<Foo> h = NewFoo();
   AcceptFoo(std::move(h)); // Fixes DoesNotBuild with std::move
}

调用 std::move() 实质上是一个名字擦除器:从概念上讲,你可以停止将 “h” 作为指针值的名字进行计数。这现在通过了唯一名字规则:通过 NewFoo() 分配的唯一指针现在只有一个名字(“h”),在 AcceptFoo() 调用中再次只有一个名字(“f”)。通过使用 std::move(),我们承诺在为其分配新值之前不会再次读取 “h”。

名称计数是现代 C++ 中一个非常有用的技巧,即使你不是 lvalues、rvalues 等方面的专家:它可以帮助你识别不必要的复制,并帮助你正确使用 std::unique_ptr。在计数后,如果发现某个点有太多名字,使用 std::move 来移除不再需要的名字。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值