- 认识std::weak_ptr
1.1 底层原理:
std::weak_ptr是一种智能指针,它指向一个std::shared_ptr管理的对象。但是,它不会增加对象的引用计数,因此,它不会影响对象的生命周期。这种指针的主要作用是协助std::shared_ptr工作,它可以访问std::shared_ptr管理的对象,但是它不拥有该对象。std::weak_ptr可以从std::shared_ptr或另一个std::weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp(sp);
std::cout << "wp.use_count() = " << wp.use_count() << std::endl; // 1
1.2 简而言之:
std::weak_ptr只有指向对象的使用权,而没有管理权。
2. 如何使用std::weak_ptr
2.1 如何读取引用对象?
weak_ptr对它所指向的shared_ptr所管理的对象没有所有权,不能对它解引用,因此若要读取引用对象,必须要转换成shared_ptr。 C++中提供了lock函数来实现该功能。如果对象存在,lock()函数返回一个指向共享对象的shared_ptr,否则返回一个空shared_ptr。
2.2 如何判断weak_ptr指向对象是否存在呢?
weak_ptr提供了一个成员函数expired()来判断所指对象是否已经被释放。如果所指对象已经被释放,expired()返回true,否则返回false。
程序示例:
std::shared_ptr<int> sp1(new int(22));
std::shared_ptr<int> sp2 = sp1;
std::weak_ptr<int> wp = sp1; // point to sp1
std::cout<<wp.use_count()<<std::endl; // 2
if(!wp.expired()){
std::shared_ptr<int> sp3 = wp.lock();
std::cout<<*sp3<<std::endl; // 22
}
2.3 由std::weak_ptr构造std::shared_ptr
std::weak_ptr可以作为std::shared_ptr的构造函数参数,但如果std::weak_ptr指向的对象已经被释放,那么std::shared_ptr的构造函数会抛出std::bad_weak_ptr异常。
std::shared_ptr<int> sp1(new int(22));
std::weak_ptr<int> wp = sp1; // point to sp1
std::shared_ptr<int> sp2(wp);
std::cout<<sp2.use_count()<<std::endl; // 2
sp1.reset();
std::shared_ptr<int> sp3(wp); // throw std::bad_weak_ptr
2.4 std::weak_ptr 一些内置方法
方法 | 用途 |
---|---|
use_count() | 返回与之共享对象的shared_ptr的数量 |
expired() | 检查所指对象是否已经被释放 |
lock() | 返回一个指向共享对象的shared_ptr,若对象不存在则返回空shared_ptr |
owner_before() | 提供所有者基于的弱指针的排序 |
reset() | 释放所指对象 |
swap() | 交换两个weak_ptr对象 |
- 应用场景
3.1 用于实现缓存
weak_ptr可以用来缓存对象,当对象被销毁时,weak_ptr也会自动失效,不会造成野指针。
假设我们有一个Widget类,我们需要从文件中加载Widget对象,但是Widget对象的加载是比较耗时的。
std::shared_ptr<Widget> loadWidgetFromFile(int id);
// a factory function which returns a shared_ptr, which is expensive to call
// may perform file or database I/O
因此,我们希望Widget对象可以缓存起来,当下次需要Widget对象时,可以直接从缓存中获取,而不需要重新加载。这个时候,我们就可以使用std::weak_ptr来缓存Widget对象,实现快速访问。如以下代码所示:
std::shared_ptr<Widget> fastLoadWidget(int id) {
static std::unordered_map<int, std::weak_ptr<Widget>> cache; // long lifetime
auto objPtr = cache[id].lock();
if (!objPtr) {
objPtr = loadWidgetFromFile(id);
cache[id] = objPtr; // use std::shared_ptr to construct std::weak_ptr
}
return objPtr;
}
当对应id的Widget对象已经被缓存时,cache[id].lock()会返回一个指向Widget对象的std::shared_ptr,否则cache[id].lock()会返回一个空的std::shared_ptr,此时,我们就需要重新加载Widget对象,并将其缓存起来,这一步会由std::shared_ptr构造std::weak_ptr。
为什么不直接存储std::shared_ptr呢?静态的std::unordered_map具有长生命周期,其中存储的std::shared_ptr会导致缓存中的对象永远不会被销毁,因为std::shared_ptr的引用计数永远不会为0。而std::weak_ptr不会增加对象的引用计数,因此,当缓存中的对象没有被其他地方引用时,std::weak_ptr会自动失效,从而导致缓存中的对象被销毁。
3.2 避免循环引用
std::weak_ptr可以用来解决使用std::shared_ptr时可能导致的循环引用问题。
- 什么是“循环引用” ?
循环引用是指两个或多个对象之间通过shared_ptr相互引用,形成了一个环,导致它们的引用计数都不为0,从而导致内存泄漏。
在观察者模式中使用shared_ptr可能会出现循环引用,在下面的程序中,Observer对象和Subject对象相互引用,导致它们的引用计数都无法减到0,从而可能导致内存泄漏。
#include <iostream>
#include <vector>
#include <memory>
#include <thread>
#include <string>
#include <algorithm>
class IObserver {
public:
virtual void update(const std::string& msg) = 0;
virtual ~IObserver() = default;
};
class Subject {
public:
void attach(const std::shared_ptr<IObserver>& observer) {
observers_.emplace_back(observer);
}
void detach(const std::shared_ptr<IObserver>& observer) {
observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end());
}
void notify(const std::string& msg) {
for (auto& observer : observers_) {
observer->update(msg);
}
}
private:
std::vector<std::shared_ptr<IObserver>> observers_;
};
class ConcreteObserver : public IObserver {
public:
ConcreteObserver(const std::shared_ptr<Subject>& subject) : subject_(subject) {}
void update(const std::string& msg) override {
std::cout << "ConcreteObserver " << msg<< std::endl;
}
private:
std::shared_ptr<Subject> subject_;
};
int main() {
std::shared_ptr<Subject> subject = std::make_shared<Subject>();
std::shared_ptr<IObserver> observer = std::make_shared<ConcreteObserver>(subject);
subject->attach(observer);
subject->notify("update");
return 0;
}
- 用std::weak_ptr避免循环引用的方法
将Observer类中的subject_成员变量改为weak_ptr,这样就不会导致内存无法正确释放了。
- 用于实现单例模式
单例模式是指一个类只能有一个实例,且该类能自行创建这个实例的一种模式。单例模式的实现方式有很多种,其中一种就是使用std::weak_ptr。
class Singleton {
public:
static std::shared_ptr<Singleton> getInstance() {
std::shared_ptr<Singleton> instance = m_instance.lock();
if (!instance) {
instance.reset(new Singleton());
m_instance = instance;
}
return instance;
}
private:
Singleton() {}
static std::weak_ptr<Singleton> m_instance;
};
std::weak_ptr<Singleton> Singleton::m_instance;
- 用std::weak_ptr实现单例模式的优点:
- 避免循环应用:避免了内存泄漏。
- 访问控制:可以访问对象,但是不会延长对象的生命周期。
- 可以在单例对象不被使用时,自动释放对象。