本周小贴士#55:名字计数与unique_ptr

Posted 飞鹤0755

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#55:名字计数与unique_ptr相关的知识,希望对你有一定的参考价值。

作为totw#55最初发表于2013年9月12日
由Titus Winters(titus@google.com)创作

于2017-10-20更新

“尽管我们可能以一千种名字来认识他,但是他于我们所有人而言是同一个人。”——圣雄甘地
通俗来讲,值的“名字”是在任意作用域内保持特定数值的任意值类型变量(不是指针也不是引用)。(对于规范律师而言,如果我们说“名字”,实质上我们谈论的是左值。)因为std::unique_ptr的特定行为的要求,我们需要确保在std::unique_ptr中保存的任意值仅有一个名字。

请务必注意,C++委员会为std::unqiue_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); // 不能编译
}

void SmarterThanTheCompilerButNot() {
  Foo* j = new Foo(2);
  // 可以编译,但是违反规则并且在运行时会重复删除
  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: 错误: 调用std::unique_ptr<Foo>被废弃了的构造函数
  AcceptFoo(g);

即便编译器没有抓到你的问题,std::unique_ptr的运行时行为也会发生。每当你“自以为超过”编译器(查看SmarterThanTheComilerButNot()),然后引入多个std::unique_ptr名字,它可能能够编译(暂时),但是你将遇到运行时内存问题。

现在问题变成了:我们如何移除名字?C++11以std::move()的形式提供了一种解决方案。

 void EraseTheName() {
   std::unique_ptr<Foo> h = NewFoo();
   AcceptFoo(std::move(h)); // 用std::move来修复DoesNotBuild
}

调用std::move实际上是一种名字消除器:从概念上讲,你可以停止计数关于指针值名字“h”。现在,这通过distinct-names规则:在用NewFoo()分配的唯一指针有单个的名字(“h”),并且在调用AcceptFoo()中,这里又仅有单个名字(“f”)。通过使用std::move,我们保证直到为它分配了新值,才会再次读取“h”。

在现代C++中,对于那些不熟悉左值、右值等细节的人而言,名字计数是一种方便的技巧:它能够帮助你识别不必要拷贝的可能性,并且它能够帮助你恰当地使用std::unique_ptr。名字计数以后,如果你在一个地方发现过多的名字,请使用std::move来删除不再需要的名字。

以上是关于本周小贴士#55:名字计数与unique_ptr的主要内容,如果未能解决你的问题,请参考以下文章

本周小贴士#101:返回值,引用和生命周期

本周小贴士#116: 拷贝消除与值传递

本周小贴士#116: 拷贝消除与值传递

本周小贴士#134:make_unique与私有构造函数

本周小贴士#112:emplace与push_back

本周小贴士#49:参数依赖查找