1. 不使用smart_ptr可能产生的问题
1.1 内存泄漏
#include <iostream>
#include <cstdlib>
int main() {
int* ptr = (int*)malloc(sizeof(int)); // 分配内存
if (ptr == NULL) {
std::cerr << "Memory allocation failed!" << std::endl;
return 1;
}
// ... 使用 ptr 访问分配的内存 ...
// 错误: 没有对 ptr 进行释放
return 0;
}
上述代码使用malloc分配内存,但没有使用free来释放,造成内存泄漏
1.2 double free
#include <iostream>
#include <cstdlib>
int main() {
int* ptr = (int*)malloc(sizeof(int)); // 分配内存
free(ptr); // 第一次释放
free(ptr); // 第二次释放,导致double free错误
return 0;
}
以上代码一个malloc对应了两个free,造成了double free的错误
1.3 野指针
void main()
{
char* p = (char *) malloc(10);
strcpy(p, “abc”);
free(p); //p所指的内存被释放,但是p所指的地址仍然不变
strcpy(p, “def”); // 错误
}
2. smart_ptr 小解
2.1 unique_ptr
独享指针,不能拷贝构造和赋值,但可以使用move转移所有权;离开作用域就自动释放独占了指针
// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
释放所有权 sp.release()
, 返回raw pointer
unique_ptr<int> p1 = make_unique<int>(1);
int* a = p1.release();
std::cout<<*a<<std::endl;
delete a; // you need to delete it manually
重置所有权 sp.reset()
, 释放所有权,指向空指针
unique_ptr<int> p1 = make_unique<int>(1);
p1.reset();
std::cout<<p1.get()<<std::endl; // 0
std::move()
可以将一个unique_ptr
转移给另一个unique_ptr
或者shared_ptr
。转移后,原来的unique_ptr
将不再拥有对内存的控制权,将变为空指针。
std::unique_ptr<int> p1 = std::make_unique<int>(0);
std::unique_ptr<int> p2 = std::move(p1);
// now, p1 is nullptr
2.2 shared_ptr
std::shared_ptr<T>
是一个类模板,它的对象行为像指针,但是它还能记录有多少个对象共享它管理的内存对象。多个std::shared_ptr<T>
可以共享同一个对象。当最后一个std::shared_ptr<T>
被销毁时,它会自动释放它所指向的对象。一个shared_ptr<T>
指针可以通过make_shared<T>
函数来创建,也可以通过拷贝或赋值另一个shared_ptr
来创建。
2.2.1 底层原理
element_type* _M_ptr; // Contained pointer.
__shared_count<_Lp> _M_refcount; // Reference counter.
std::shared_ptr
在内部维护一个引用计数,其只有两个指针成员,一个指针是所管理的数据的地址;还有一个指针是控制块的地址,包括引用计数、weak_ptr计数、删除器(Deleter)、分配器(Allocator)。因为不同shared_ptr指针需要共享相同的内存对象,因此引用计数的存储是在堆上的。而unique_ptr只有一个指针成员,指向所管理的数据的地址。因此一个shared_ptr对象的大小是raw_pointer大小的两倍。
2.2.2 什么时候用shared_ptr
通常用于一些资源创建昂贵比较耗时的场景, 比如涉及到文件读写、网络连接、数据库连接等。当需要共享资源的所有权时,例如,一个资源需要被多个对象共享,但是不知道哪个对象会最后释放它,这时候就可以使用std::shared_ptr<T>
。
2.3 weak_ptr
弱引用,指向shared_ptr所管理的对象,而不影响所指对象的生命周期,也就是将一个weak_ptr
绑定到一个shared_ptr
不会改变shared_ptr
的引用计数。不论是否有weak_ptr
指向,一旦最后一个指向对象的shared_ptr
被销毁,对象就会被释放。
2.3.1 读取引用对象
weak_ptr
对它所指向的shared_ptr
所管理的对象没有所有权,不能对它解引用,因此若要读取引用对象,必须要转换成shared_ptr
。 C++中提供了lock函数来实现该功能。如果对象存在,lock()
函数返回一个指向共享对象的shared_ptr
,否则返回一个空shared_ptr
2.3.2 weak_ptr
指向对象是否存在
weak_ptr
提供了一个成员函数expired()
来判断所指对象是否已经被释放。如果所指对象已经被释放,expired()返回true,否则返回false。
2.3.3 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.3.4 使用场景
2.3.4.1 缓存
使用std::weak_ptr
来缓存Widget对象,实现快速访问。
std::shared_ptr<Widget> fastLoadWidget(int id) {
static std::unordered_map<int, std::weak_ptr<Widget>> cache;
auto objPtr = cache[id].lock();
if (!objPtr) {
objPtr = loadWidgetFromFile(id);
cache[id] = objPtr; // use std::shared_ptr to construct std::weak_ptr
}
return objPtr;
}
为什么不直接存储std::shared_ptr
呢?因为这样会导致缓存中的对象永远不会被销毁,因为std::shared_ptr
的引用计数永远不会为0。而std::weak_ptr
不会增加对象的引用计数,因此,当缓存中的对象没有被其他地方引用时,std::weak_ptr
会自动失效,从而导致缓存中的对象被销毁。
2.3.4.2 解决循环引用
class IObserver {
public:
virtual void update(const string& msg) = 0;
};
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 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 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;
}
以上的代码会造成循环引用;
解决方法:将Observer类中的subject_成员变量改为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
实现单例模式的优点:
- 避免循环应用:避免了内存泄漏。
- 访问控制:可以访问对象,但是不会延长对象的生命周期。
- 可以在单例对象不被使用时,自动释放对象。
文档信息
- 本文作者:Junyao Gu
- 本文链接:https://gujunyao.top/2024/08/01/smartPtr/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)