一、unique_ptr
unique_ptr是在auto_ptr的基础之上,解决了多个智能指针同时指向一个对象,发生管理权转移,只有一个智能指针指向了对象,其他的都是管理的空对象的行为。这里的多个智能指针指向同一个对象是通过拷贝构造或者赋值重载实现的,unique_ptr的解决办法就是将这两种方式禁用掉,不让其进行这类操作,保证了同一时间只有一个智能指针指向该对象。
1.构造函数与析构函数
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(1);
std::unique_ptr<MyClass> ptr2(new MyClass(1));
上述的是C++标准库的版本,第一个属于标准库提供的函数,用来在堆上创建一个MyClass对象,并将该对象传递给智能指针,两个其实都是调用的构造函数。所以构造函数的实现就很简单,将这个申请的堆空间的地址,传递给该智能指针内部的变量即可。
析构函数也能简单,智能指针的作用就是用于将申请到的空间的存在周期和智能指针对象的生命周期进行绑定,所以智能指针释放的时候,就需要释放堆空间了。
2.移动构造和移动拷贝
不可以使用拷贝构造和赋值重载是因为使用之后,会产生智能指针管理空对象,如果在使用该智能指针的话,就会出现问题了,但是对于将亡值这类,就不存在了,这类对象的管理权转移给别人之后他们也就销毁了。所以不存在管理空对象的智能指针问题。
3. 其他函数
还要实现的是指针的相关操作,如*和->的运算符重载函数,还要获取该管理对象的函数get函数。
4.代码实现与测试
#include <iostream>// 测试代码
class MyClass
{
public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }void doSomething() { std::cout << "Doing something..." << std::endl; }
};namespace ns_ptr
{template<class T>class unique_ptr{private:T* _ptr;// 禁用拷贝构造和赋值重载unique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;public:// 构造函数unique_ptr(T* space) :_ptr(space){}// 析构函数~unique_ptr() { if (_ptr != nullptr) delete _ptr; }// 移动拷贝和移动赋值unique_ptr(unique_ptr&& other): _ptr(other._ptr){other._ptr = nullptr;}unique_ptr& operator=(unique_ptr&& other){if(this != &other){// 释放旧对象delete _ptr;// 管理新对象_ptr = other._ptr;other._ptr = nullptr;}}// 指针的相关操作T& operator*() { return *_ptr; }T* operator->() { return _ptr; }//获取该指针对象T* get() { return _ptr; }};
}int main()
{ns_ptr::unique_ptr<MyClass> ptr1(new MyClass());ptr1->doSomething();// 转移所有权ns_ptr::unique_ptr<MyClass> ptr2 = std::move(ptr1);if (!ptr1.get()) {std::cout << "ptr1 is empty after move" << std::endl;}(*ptr2).doSomething();return 0;
}
二、shared_ptr
shared_ptr允许同一时间有多个智能指针同时指向同一个对象,并在内部维护一个引用计数,记录有多少个智能指针指向这个对象。那么这个智能指针就不需要禁用拷贝构造和赋值重载函数了。这里的引用计数必须实时的在每一个智能指针内部更新,所以我们需要将引用计数单独提取出来,而不是有一个新的智能指针就去挨个更改其他智能指针内部的引用计数的值。所以这里单独设计了一个引用计数类。
1.构造函数与析构函数
shared_ptr类内部有两个变量,一个是指针,另一个是引用计数类对象。当初始化的时候,会创建一个引用计数类,并初始化值为1。析构该对象的时候,会将引用计数减一,然后去判断引用计数是否为0了,如果为0的话,就可以进行析构管理的空间了。
2.拷贝构造和赋值重载函数
他俩类似,都是将other内部的两个对象进行值拷贝,然后将引用计数加一即可,赋值重载额外需要对管理的就对象考虑是否要析构。
3. 其他函数
还要实现的是指针的相关操作,如*和->的运算符重载函数,还要获取该管理对象的函数get函数与获取引用计数值的函数。
4.代码实现与测试
#include<iostream>namespace ns_ptr
{// 引用计数类template<class T>class ref_count{private:int _count;public:// 构造函数与析构函数ref_count() : _count(1) {}~ref_count() {}// 引用计数的增减void addCount() { ++_count; }int delCount() { return --_count; }// 获取引用计数int getCount() { return _count; }};// 智能指针类template<class T>class shared_ptr{private:ref_count<T>* _ref; // 引用计数类T* _ptr;public:// 构造函数与析构函数shared_ptr(T* space) : _ptr(space), _ref(new ref_count<T>()){}~shared_ptr(){// 减少引用计数_ref->delCount();// 获取引用计数if (_ref->getCount() == 0){delete _ptr;delete _ref;}}// 拷贝构造和赋值重载函数shared_ptr(const shared_ptr& other): _ptr(other._ptr), _ref(other._ref){// 增加引用计数_ref->addCount();}shared_ptr& operator=(const shared_ptr& other){if (this != &other){// 引用计数减少1,如果为0了,就析构旧对象if (_ref->delCount() == 0){delete _ptr;delete _ref;}_ref = other._ref;_ref->addCount();_ptr = other._ptr;}return *this;}// 解引用相关运算符T& operator*() { return *_ptr; }T* operator->() { return _ptr; }// 获取引用计数int use_count() { return _ref->getCount(); }};
}// 测试代码
class MyClass {
public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }void doSomething() { std::cout << "Doing something..." << std::endl; }
};int main() {// 创建一个 MySharedPtr 对象ns_ptr::shared_ptr<MyClass> ptr1(new MyClass());std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;// 拷贝构造ns_ptr::shared_ptr<MyClass> ptr2(ptr1);std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;// 赋值操作ns_ptr::shared_ptr<MyClass> ptr3(ptr2);ptr3 = ptr1;std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;std::cout << "ptr3 use count: " << ptr3.use_count() << std::endl;// 调用成员函数ptr1->doSomething();return 0;
}