C++智能指针 unique_ptr、shared_ptr 的实现
shared_ptr
shared_ptr 是通过引用计数的方式实现的,这里结合一个单线程的参考实现进行解释。注意,这种实现并不是线程安全的。STL 的 std::shared_ptr 也不是线程安全的,两个成员变量的修改并不是一次操作完成的,使用时需要加锁。
参考实现
namespace smart_pointer {
template<typename T>
class SharedPtr {
public:
explicit SharedPtr (T* ptr = nullptr) : _ptr(ptr), _pcount(new int(1)) {
}
SharedPtr (const SharedPtr& s) : _ptr(s._ptr), _pcount(s._pcount) {
*(this->_pcount)++;
}
~SharedPtr () {
if (--(*(this->_pcount)) == 0) {
delete this->_ptr;
delete this->_pcount;
this->_ptr = nullptr;
this->_pcount = nullptr;
}
}
SharedPtr& operator= (const SharedPtr& s){
if (this != &s) {
if (--(*(this->_pcount)) == 0) {
delete this->_ptr;
delete this->_pcount;
}
this->_ptr = s._ptr;
this->_pcount = s._pcount;
*(this->_pcount)++;
}
return *this;
}
T& operator*() {
return *(this->_ptr);
}
T* operator->() {
return this->_ptr;
}
operator bool () {
return this->_ptr;
}
T* get() const {
return this->_ptr;
}
void swap(SharedPtr& s) {
std::swap(this->_ptr, s._ptr);
std::swap(this->_pcount, s._pcount);
}
private:
T* _ptr; // 指向底层对象的指针
int* _pcount; // 指向引用计数的指针
};
template<typename T>
void swap(SharedPtr<T>& a, SharedPtr<T>& b) {
a.swap(b);
}
}; // namespace smart_pointer
int main() {
SharedPtr<int> p1, p2;
std::swap(p1, p2);
return 0;
}
代码说明
- 拷贝赋值的时候,要考虑当前所持有的对象是否应该被释放,然后再改变所指对象。
- 为了提供程序健壮性,保证
std::swap
在智能指针上能正确工作,需要特化智能指针类型。参考 Effictive C++ 第25款,对其进行特化。由于C++只允许对class template进行偏特化,不允许对function template偏特化,我们想把SharedPtr<T>::swap
全部加入std
是不可行的。解决办法是利用C++的名称查找规则,在SharedPtr的命名空间中提供类型匹配的swap,使编译器优先选择该函数使用。 - 这种实现方案,在每个底层对象之外,额外开辟了一个int的空间用于引用计数,并由多个指针共享访问。相比于 unique_ptr,shared_ptr 使用了两个指针,多一倍的内存开销。相比于单指针的实现方案,好处是在高频的对象访问上,减少了一次间接访问。
单指针实现方案
namespace smart_pointer {
template<typename T>
class SharedPtr {
public:
explicit SharedPtr (T* ptr = nullptr) : _pholder(new ObjectHolder(ptr)) {
}
SharedPtr (const SharedPtr& s) : _pholder(s._pholder) {
this->_pholder->_pcount++;
}
~SharedPtr () {
if (--(this->_pholder->_pcount) == 0) {
delete this->_pholder;
this->_pholder = nullptr;
}
}
SharedPtr& operator= (const SharedPtr& s){
if (this != &s) {
if (--(this->_pholder->_pcount) == 0) {
delete this->_pholder;
}
this->_pholder = s._pholder;
this->_pholder->_pcount++;
}
return *this;
}
T& operator*() {
return *(this->_pholder->_ptr);
}
T* operator->() {
return this->_pholder->_ptr;
}
operator bool () {
return this->_pholder->_ptr;
}
T* get() const {
return this->_pholder->_ptr;
}
void swap(SharedPtr& s) {
std::swap(this->_pholder, s._pholder);
}
private:
class ObjectHolder {
friend class SharedPtr; // 内部类可以任意访问外部类的成员,外部类只有作为友元才能访问内部类私有成员
public:
explicit ObjectHolder(T* ptr = nullptr) : _ptr(ptr), _pcount(1) {
}
~ObjectHolder() {
delete _ptr;
}
private:
T* _ptr; // 指向底层对象的指针
int _pcount; // 引用计数,注意不是指针了
}; // 每个对象由一个ObjectHolder持有
ObjectHolder* _pholder; // 指向计数对象的指针
};
template<typename T>
void swap(SharedPtr<T>& a, SharedPtr<T>& b) {
a.swap(b);
}
}; // namespace smart_pointer
unique_ptr
unique_ptr 使用了 RAII 管理资源,保证了出现异常的情况下,资源也能正确释放。
参考实现
namespace smart_pointer {
template <typename T>
class PointerDeleter{
public:
void operator() (const T *_ptr) {
if(_ptr) {
delete _ptr;
_ptr = nullptr;
}
}
};
template <typename T, typename Deleter = PointerDeleter<T> >
class UniquePtr {
public:
explicit UniquePtr (T *ptr = nullptr) : _ptr(ptr) {
}
~UniquePtr () {
Deleter()(this->_ptr);
}
// non-copyable
UniquePtr (const UniquePtr &p) = delete;
UniquePtr& operator= (const UniquePtr &p) = delete;
// move constructor
UniquePtr (UniquePtr &&p) : _ptr(p._ptr) {
p._ptr = nullptr;
}
// move assignment
UniquePtr& operator= (UniquePtr &&p) {
std::swap(this->_ptr, p._ptr);
return *this;
}
T& operator*() {
return *(this->_ptr);
}
T* operator->() {
return this->_ptr;
}
operator bool () {
return this->_ptr;
}
T* get() const {
return this->_ptr;
}
T* release() {
T *pointer = this->_ptr;
this->_ptr = nullptr;
return pointer;
}
void reset (T *ptr) {
UniquePtr<T, Deleter>().swap(*this);
this->_ptr = ptr;
}
void swap(UniquePtr &p) {
std::swap(this->_ptr, p._ptr);
}
private:
T *_ptr;
};
template<typename T>
void swap(UniquePtr<T>& a, UniquePtr<T>& b) {
a.swap(b);
}
}; // namespace smart_pointer
代码说明
UniquePtr 需要注意的是保证不会被复制,可以采用的办法有两种。以前的解决办法是把拷贝构造函数和拷贝赋值操作符设为private禁止访问,新的解决办法是将两个函数标记为 delete
。我们这里采用的是新标准。