实现简易智能指针
#include<iostream>
using namespace std;
// 智能指针 保证能做到资源的自动释放
// 智能指针实际利用栈上的对象出作用域自动析构的特征,来做到资源的自动释放
// 因为裸指针是个堆,所以需要手动释放对象,现在写成类后,就可以用栈来自动释放对象了
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr)
:mptr(ptr) {}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; } //必须是T&,否则不能修改mptr指向的对象的值,反而是会创建一个临时副本,把值传给临时副本,而不是原对象
T* operator->() { return mptr; }
private:
T *mptr;
};
int main()
{
CSmartPtr<int> ptrl(new int);
*ptrl = 20;// 这一行使用了重载的解引用运算符来访问 ptrl 所管理的 int 对象,并将其值设置为 20。
class Test
{
public:
void test() { cout << "call Test::test" << endl; }
};
CSmartPtr<Test> ptr2(new Test());
// (ptr2->operator->())->test();
ptr2->test();
return 0;
}
// 智能指针放在堆上
// CSmartPtr<int> *p = new CSmartPtr<int>(new int); delete p;这时候p是指向堆的智能指针,所以也要手动释放,这样就没啥意义了,还不如直接使用裸指针
// 在这段代码中,p 是一个指向 CSmartPtr<int> 类型的指针。*p 是一个 CSmartPtr<int> 类型的对象。
// 在这里,new int 是一个表达式,它使用 new 运算符动态分配内存来存储一个 int 类型的对象,并返回一个指向该对象的指针。这个指针被传递给 CSmartPtr<int> 的构造函数作为参数
问题
我们继续看下面的代码
CSmartPtr<int> p1(new int);
CSmartPtr<int> p2(p1);
这样的话,是会报错的,因为这会造成一个浅拷贝的问题(等以后再详细解释
那么为了解决浅拷贝的问题,我们用不带引用计数的智能指针和带引用计数的智能指针来解决
不带引用计数的智能指针
不带引用计数的:是只能一个指针管理资源
auto_ptr:C++库里面的
C++11新标准:scoped_ptr和unique_ptr
首先说auto_ptr
auto_ptr<int> p1(new int);
auto_ptr<int> p2(p1);
这样是可以正确运行的
但我们继续
auto_ptr<int> p1(new int);
auto_ptr<int> p2(p1);
*p2 = 20;
cout << *p1 << endl;
这是会报错的,那为什么呢,p1和p2不都是指向同一块内存吗
这时候咱们可以看下auto_ptr的拷贝构造函数看看了
auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
_Ty* release() noexcept {
_Ty* _Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
_Right就是咱们传入的p1了,p1调用release后,返回值初始化p2,在源代码里其实就是_Myptr,_Myptr查看源码,它是成员变量,也就是auto_ptr封装的裸指针
private:
_Ty* _Myptr;
其实auto_ptr在里面是这样走的,首先是把当前指针p1咱们的值先记下来,然后把当前指针p1改成nullptr,最后再把原来的值返回回去给p2
你把上面源代码release中的_Myptr换成p1就好理解了
但这样就带来了一个问题,那就是auto_ptr每次都是只记住最后一个指针,前面的指针都为空了
但我们的本意是想让p1和p2都可以访问到这个地址,所以这样说的话auto_ptr是有些问题的,所以你也能看到一般也都是不推荐使用auto_ptr
经常会被问到能不能再容器当中使用auto_ptr,其实尽量是别搞
vector<auto_ptr<int>> vec1;
vec2(vec1);
因为容器在使用过程中,难免会用到容器的拷贝构造或者容器的赋值,而这样的话,会影响容器内每个元素的拷贝赋值
当你用vec1构造vec2的时候,那就说明vec1里面的指针,全部为空,当你在不知道这个的情况下,你使用vec1里面的智能指针,就全部都是空指针了
既然auto_ptr尽量不用,那scoped_ptr呢
我们先看scoped_ptr的拷贝构造函数和拷贝赋值运算符
scoped_ptr(const scoped_ptr<T>&) = delete;
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;
这些语句定义了scoped_ptr的拷贝构造函数和拷贝赋值运算符,它们使用了C++11中的关键字来禁用了这些函数
这意味着你不能使用拷贝构造函数或拷贝赋值运算符来创建一个 scoped_ptr对象的副本,如果你尝试这样做,编译器将报错
其实scoped_ptr的拷贝构造函数是被声明为private并且是没被定义的,这意味着你不能使用拷贝构造函数来创建一个 scoped_ptr对象的副本
这是为了防止多个 scoped_ptr对象管理同一个资源,从而避免在其中一个 scoped_ptr对象销毁时释放资源,导致其他 scoped_ptr 对象悬空
所以 scoped_ptr就不能这样写,这样写就是错的
scoped_ptr<int> p1(new int);
scoped_ptr<int> p2(p1);
那该怎么办呢,也就只剩下unique_ptr了
我们看它的拷贝构造函数和拷贝赋值运算符
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
和上面scoped_ptr是一样的,那说明我们这样写也是错的
unique_ptr<int> p1(new int);
unique_ptr<int> p2(p1);
但是如果这样写呢
uniq