对于某些对象,我们希望第一次访问的时候才创建。以前可以用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 new
在alignas(T) char storage_[sizeof(T)]
预先分配好的内存上创建对象。返回的是对象指针,就不会主动调用析构函数,也不会回收内存。
关键字alignas
则是用于分配对象内存对齐。