自己的知乎登不上去了,借这个机会转战csdn吧哈哈。为了保证C++这个系列的完整性,先把之前的文章迁移过来。
智能指针介绍
今天主要讲的是C++的智能指针。在C++中,智能指针的引入是为了更加安全和方便的动态管理内存。智能指针大体上和普通的指针具有一样的功能,唯一不同的地方在于它可以自动的delete其所指向的object。在正确使用的前提下,智能指针可以很好的避免因为忘记释放内存而导致内存泄漏的事情发生。总的来说,C++定义了两种不同的智能指针,shared_ptr和unique_ptr。
shared_ptr
允许多个指针指向同一个对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数 加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁。
unique_ptr
只允许一个指针指向这个对象,也可以理解为这个unique_ptr独自拥有这个对象。
weak_ptr
除了这两种智能指针之外,C++还定义了一种weak_ptr。weak_ptr是对于一个shared_ptr指向对象的弱引用,是为了配合shared_ptr而引入的一种智能指针,它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作。以上所有的智能指针都是在memory这个header中定义的,接下来我们重点讲解shared_ptr的内容。
shared_ptr使用方式
- 初始化
shared_ptr<int> p1 = make_shared<int>(15); //p1指向值为15的int shared_ptr<int> p2 = make_shared<int>(); //p2指向值为0的int std::shared_ptr<Person> p1(new Person(1)); std::shared_ptr<Person> p2 = std::make_shared<Person>(2);
- 拷贝与重置
struct MyInt{ int val; MyInt(int value) : val(value){} }; shared_ptr<MyInt> p1 = make_shared<MyInt>(new MyInt(15)); // p1指向值为15的MyInt p1.reset(new MyInt(3));// 首先生成新对象,然后引用计数减1,引用计数为0,故析构MyInt(15),最后将新对象的指针交给智能指针 shared_ptr<MyInt> p2(p1); // 现在MyInt 3的引用计数为2 p2.reset() // MyInt 3现在引用计数变为1 p1.reset() // MyInt 3现在引用计数变为0
- 获取原始指针
shared_ptr<MyInt> p1(new MyInt(15)); MyInt* p2 = p1.get(); // 注意这里获得的原始指针p2可能会随p1的自动回收而变成野指针
- 其他相关操作
shared_ptr<int> p1 = make_shared<int>(15); p1.unique(); // returns true if p1.use_count() == 1 p1.use_count(); // returns number of object sharing with p1. 这个操作有可能会很慢,所以除了debug之外慎用!
shared_ptr需要注意的问题
- shared_ptr多次引用同一数据,会导致两次释放同一内存。
- shared_ptr循环引用问题。这个时候就要用到前面提到的weak_ptr。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加,从而打破了之前所说的循环引用。值得注意的是,使用weak_ptr的成员函数lock()可以从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。
- 线程安全问题特别是两个不同的线程同时增加一个shared_ptr的引用或者同时有一个shared_ptr的析构函数被调用时,这个shared_ptr的引用计数有可能会被两个线程同时操作,造成潜在的race condition。在下面的实现中我也用mutex lock避免了这个问题。要注意的是shared_ptr本身只能保证引用计数的线程安全,对于指向的object进行的操作并不能保证线程安全,所以需要额外加锁。
unique_ptr相关
unique_ptr大体与shared_ptr类似,不同点在于它的release()成员函数可以让其放弃它所指对象的控制权,并返回保存的指针,同时将unique_ptr设为空,不会释放内存。
智能指针实现
在这里我放一个自己写的shared_ptr的类,里面可能还有些问题,并且也有一些接口比如swap和重载bool没有实现,欢迎大家在评论区一起讨论。
#ifndef MY_SHARED_PTR_H_
#define MY_SHARED_PTR_H_
#include <mutex>
template <typename T>
class my_shared_ptr {
public:
// constructors
my_shared_ptr(T* other)
: ptr_(other), refcnt_(new int(1)), refcnt_lock_(new std::mutex) {}
// copy constructor
my_shared_ptr(const my_shared_ptr<T>& other)
: ptr_(other.ptr_),
refcnt_(other.refcnt_),
refcnt_lock_(other.refcnt_lock_) {
inc_ref_count();
}
// destructor
~my_shared_ptr() { release(); }
// Replaces the managed object with the one managed by other.
my_shared_ptr<T>& operator=(const my_shared_ptr<T>& other) noexcept {
if (ptr_ != other.ptr_) {
release();
ptr_ = other.ptr_;
refcnt_ = other.refcnt_;
refcnt_lock_ = other.refcnt_lock_;
inc_ref_count();
}
return *this;
}
// observer
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
// Return the stored pointer.
T* get() const noexcept { return ptr_; }
void reset(T* ptr = nullptr) noexcept {
if (ptr != ptr_) {
if (ptr_) delete ptr_;
ptr_ = ptr;
reset_ref_count();
}
}
long use_count() const noexcept { return *refcnt_; }
bool unique() const noexcept { return use_count() == 1; }
private:
std::mutex* refcnt_lock_;
int* refcnt_;
T* ptr_;
void inc_ref_count() {
refcnt_lock_->lock();
(*refcnt_)++;
refcnt_lock_->unlock();
}
void reset_ref_count() {
refcnt_lock_->lock();
*refcnt_ = 1;
refcnt_lock_->unlock();
}
void release() {
refcnt_lock_->lock();
(*refcnt_)--;
if (*refcnt_ == 0) {
delete ptr_;
delete refcnt_;
refcnt_lock_->unlock();
delete refcnt_lock_;
}
refcnt_lock_->unlock();
}
};
#endif
除此之外我还写了一个unique_ptr的实现,并用gtest附上了相应的testcase,一起放在了这个仓库里
github · my_smart_pointer
虽然是个很小的repo,但是由于接口没有写的很全,又或者会有这样那样的问题,欢迎大家来pr或者在评论区讨论,一起学习!这一期就先到这里,下一篇想讲讲lambda表达式。