no destructor


发布于 2019-02-16


对于某些对象,我们希望第一次访问的时候才创建。以前可以用base::LazyInstance,现在chromium代码推荐用base::NoDestructor来代替。

base::NoDestructor本质上一个静态的局部变量:

  • 只会在第一次访问的时候调用构造函数创建
  • 不会调用到析构函数

下面是一个示例:

class People {
 public:
  People(const std::string& name) : name_(name) {
    std::cout << this << " constructor ";
    std::cout << name_ << std::endl;
  }

  ~People() {
    std::cout << this << " destructor ";
    std::cout << name_ << std::endl;
  }

  void SayHello() {
    std::cout << this << " " << name_;
    std::cout << " hello" << std::endl;
  }

 private:
  std::string name_;
};

People& GetPeopleRef() {
  static base::NoDestructor<People> p("li");
  return *p;
}

People* GetPeoplePtr() {
  static base::NoDestructor<People> p("wang");
  return p.get();
}


  People& people3 = GetPeopleRef();
  people3.SayHello();

  People& people4 = GetPeopleRef();
  people4.SayHello();

  GetPeopleRef().SayHello();
  GetPeopleRef().SayHello();

  People* people5 = GetPeoplePtr();
  people5->SayHello();

  People* people6 = GetPeoplePtr();
  people6->SayHello();


输出:
00EE701C constructor li
00EE701C li hello
00EE701C li hello
00EE701C li hello
00EE701C li hello
00EE702C constructor wang
00EE702C wang hello
00EE702C wang hello

最佳的实践是在一个函数里创建一个静态的base::NoDestructor,然后返回对象引用或者指针。

对于返回对象的引用,后续不要赋值拷贝,也不要定义base::NoDestructor的全局函数,否则就破坏了base::NoDestructor的原则。

因为base::NoDestructor不会调用到析构函数,因此可能会存在内存泄漏的情况。而之前的base::LazyInstance有两种模式,base::LazyInstance<>::DestructorAtExit,base::LazyInstance<>::Leaky

c++ 11 之后,静态的局部变量初始化是线程安全的。我们来看看base::NoDestructor的实现,其实非常简单:

template <typename T>
class NoDestructor {
 public:
  // Not constexpr; just write static constexpr T x = ...; if the value should
  // be a constexpr.
  template <typename... Args>
  explicit NoDestructor(Args&&... args) {
    new (storage_) T(std::forward<Args>(args)...);
  }

  // Allows copy and move construction of the contained type, to allow
  // construction from an initializer list, e.g. for std::vector.
  explicit NoDestructor(const T& x) { new (storage_) T(x); }
  explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); }

  NoDestructor(const NoDestructor&) = delete;
  NoDestructor& operator=(const NoDestructor&) = delete;

  ~NoDestructor() = default;

  const T& operator*() const { return *get(); }
  T& operator*() { return *get(); }

  const T* operator->() const { return get(); }
  T* operator->() { return get(); }

  const T* get() const { return reinterpret_cast<const T*>(storage_); }
  T* get() { return reinterpret_cast<T*>(storage_); }

 private:
  alignas(T) char storage_[sizeof(T)];
};

使用template <typename... Args>可变模板参数,再配合std::forward<Args>(args)...把模板构造函数参数完美转发给实际类的构造函数。

具体new对象的时候,通过placement newalignas(T) char storage_[sizeof(T)]预先分配好的内存上创建对象。返回的是对象指针,就不会主动调用析构函数,也不会回收内存。

关键字alignas则是用于分配对象内存对齐。