Chromium智能指针使用指南

Posted bigben0123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Chromium智能指针使用指南相关的知识,希望对你有一定的参考价值。

Chromium智能指针使用指南


0.1252017.11.05 23:26:44字数 2,052阅读 1,773

什么是智能指针?

智能指针是一种特殊类型的“局部对象”,表现如同裸指针,但是具备离开作用域(out of scope)时主动释放所指向对象的能力。因为C++没有垃圾回收机制,因此智能指针的特性显得非常重要。

下面是最常用智能指针类型std::unique_ptr<>的例子:

  // 我们可以在构造std::unique_prt<>的时候传入指针
  std::unique_ptr value(base::JSONReader::Read(data));
  std::unique_ptr foo_ptr(new Foo(...));
  
  // ...或者使用reset()
  std::unique_ptr bar_ptr;      // 与 "Bar* bar_ptr = nullptr;" 相似.
  bar_ptr.reset(new Bar(...));  // 此时 |bar_ptr| 不为空且持有对象 
  
  // 我们可以用 () 检查std::unique_ptr<>是否为空
  if (!value)
    return false;
  
  // get() 访问持有的裸指针
  Foo* raw_ptr = foo_ptr.get();
  
  // 我们可以像使用裸指针一样调用std::unique_ptr<>的方法
  DictionaryValue* dict;
  if (!value->GetAsDictionary(&dict))
    return false;

为什么我们要使用智能指针?

即使对象的创建和析构时机不确定,使用智能指针确保我们能正确释放对象。无论方法里有再多且逻辑复杂的路径,智能指针总能确保局部变量正确的释放,且能明确对象的所有权,避免程序内存泄漏或者对象重复释放。最后,在方法调用时,需要明确指出对象拥有权的转移和结果。

存在哪些类型的智能指针?

在Chromium里最常用的两种智能指针类型是std::unique_ptr<>scoped_refptr<>。前者适用于单一所有权的对象,后者适用于引用计数的对象(然而,通常应该避免使用引用计数的对象)。如果你比较熟悉C++11,会发现scoepd_refptr<>std::shared_ptr<>用法很相似。

base/memory/ 还定义了其余几种类型的对象:

  • linked_ptr<> 用于在C++11之前存放智能指针对象,已被废弃。现在Chromium已经支持C++11了,我们不应该再使用linked_ptr<>了,而应该在STL容器里使用std::unique_ptr<>
  • ScopedVector<>也被废弃了。它是一种vector,并且持有容器内对象的所有权。请使用std::vector<std::unique_ptr<>>代替。
  • WeakPtr<>实际上不是智能指针。它的表现像指针类型,但是并不能用来自动释放对象,通常用作追踪其它地方拥有的对象是否依然存活,当追踪对象释放时,WeakPtr<>会自动的置为null。(但是依然需要在解引用前判断是否为null,因为解引用null WeakPtr<>等于于解引用null,而不是no-op。)WeakPtr<>与C++11的std::weak_ptr<>作用比较相似,但是使用了不同的API并且少了许多使用限制。

如何选择使用哪种智能指针?

  • 单一所有权的对象。使用std::unique_ptr。需要注意的是,std::unique_ptr持有的需要必须是非引用计数的,并且分配在堆上的对象。
  • 无所有权的对象。使用裸指针或者WeakPtr<>。注意WeakPtr<>只能在创建它的线程解引用(通常使用WeakPtrFactory<>)。如果你需要在对象释放前后立刻执行某些操作,那么可能使用callback或notification更适合,而不是WeakPtr<>
  • 引用计数的对象。使用scoped_refptr<>,但是最好是重新考虑使用引用计数对象是否合理。引用计数对象很难明确拥有权和析构顺序,特别是在多线程环境中。总是有方法来重新设计引对象层级来避免引用计数的。限制每个类都只能在单个线程工作,并且使用PostTask()确保调用在正确的线程,这样有助于在多线程中避免引用计数。base::Bind()WeakPtr<>等工具具备在对象释放时自动取消方法调用的能力。Chromium中依然有许多代码在使用引用计数对象,如果你看见Chromium中有代码这样做但并不代表这是合理的解决方案。
  • 平台特定类型。使用平台特定的对象,譬如base::win::ScopedHandlebase::win::ScopedComPtr,或者base::mac::ScopedCFTypeRef。需要注意的是这些类型使用方式可能和std::unique_ptr<>不同。

不同类型指针间调用规定是怎样的?

calling conventions section of the Chromium style guide有规定。下面列出一些常用的规定。

  • 如果方法参数里使用std::unique_ptr<> ,说明该方法需占用传入参数的所有权,调用方需要使用std::move()来表明转移对象的所有权。需要注意的是,临时对象不需要调用std::move()转移所有权。

      // Foo() 拥有 |bar| 的所有权.
      void Foo(std::unique_ptr<Bar> bar);
      
      ...
      std::unique_ptr<Bar> bar_ptr(new Bar());
      Foo(std::move(bar_ptr));          // 调用后,|bar_ptr| 被置为 null.
      Foo(std::unique_ptr<Bar>(new Bar()));  // 临时对象不需要调用std::move()
    
  • 如果方法的返回值使用std::unique_ptr<>,说明调用方需要持有返回对象的所有权。这种情况下,当且仅当返回对象类型和临时对象的类型不同时,需要使用std::move()

      class Base { ... };
      class Derived : public Base { ... };
      
      // Foo 拥有|base|的所有权, 调用方拥有 返回值对象 的所有权
      std::unique_ptr<Base> Foo(std::unique_ptr<Base> base) {
        if (cond) {
          // 转移 |base| 的所有权给调用方
          return base;                           
        }
        
        // 注意这种场景下,方法运行结束时,|base|会被释放掉
        if (cond2) {
          // 临时对象不需要调用std::move()
          return std::unique_ptr<Base>(new Base()));  
        }
        std::unique_ptr<Derived> derived(new Derived());
        // 注意需要使用std::move(),因为|derived|的类型和返回值的类型不同。
        return std::move(derived);
      }
    
  • 如果方法传入或者返回裸指针,表示无需所有权转移。Chromium在std::unique_ptr<>存在之前写的一些代码,或者不熟悉所有权转移的程序员写的代码,可能会在传入或者返回裸指针的时候也使用std::move()转移了所有权。但是这样做是不安全的,编译器并不能执行正确的表现。去掉这样的代码吧,方法传入或者返回裸指针时,绝对不要转移所有权。

可以通过引用传递参数或者返回值吗?

不要这样做。

原理上来说,传入const std::unique_ptr<T> &参数并且不转移所有权比传入T*有优势,这样做可以防止调用方传入错误的参数(譬如把 int 转成了 T*),而且调用方必须确保方法调用周期内传入对象不会被释放。但是,这样调用方就必须把传入对象生成在堆上,即使调用方原本可以使对象生成在栈上。这里传入裸指针相比传入const std::unique_ptr<T> &的好处是,可以将对象所有权的问题和对象生成的问题解耦。为了简洁和统一,我们避免开发人员去权衡这些利弊,总是使用裸指针就好了。

有个例外,在lambda表达式中,若将智能指针放在STL容器里作为参数传递,这里为了编译通过,必须使用const std::unique_ptr<T> &

我想使用STL容器用来持有指针对象。此时可以用智能指针吗?

可以。在C++11里,你可以将智能指针放入STL容器内。而且,不要再使用ScopedVector<T>了,使用std::vector<std::unique_ptr<T>>来替代。同样的,再也不要再使用linked_ptr<T>了,直接把智能智能放在STL容器里使用即可。

引用资料

以上是关于Chromium智能指针使用指南的主要内容,如果未能解决你的问题,请参考以下文章

指针辨析:悬垂指针哑指针野指针智能指针

[Chromium]怎样安全的使用PostTask

[C++11]shared_ptr共享智能指针的初始化与使用

Android系统的智能指针(轻量级指针强指针和弱指针)的实现原理分析

如何设置 vscode 的代码片段,以便在自动完成后自动触发 vscode 的智能感知?

更新:C++ 指针片段