智能指针和裸指针,平时使用的指针就是智能指针,类似于int *p=new int(10);
,罗指针存在一个问题,程序没有释放裸指针,那么就会导致内存的泄露。智能指针能自动管理好指针的开辟,指针的释放,保证做到资源的自动释放。(其实也就是利用栈上对象出作用域自动析构的特征,来做到资源的自动释放)
#include
using namespace std;
// 智能指针,使用类来管理指针,在作用域结束后,指针会自动析构
template <typename T>
class SmartPointer
{
public:
SmartPointer(T *ptr = nullptr) : mptr(ptr){};
~SmartPointer() { delete mptr; mptr=nullptr; }
T &operator*() { return *mptr; }
T *operator->() { return mptr; }
// 防止使用new智能指针导致内存泄漏
void *operator new(size_t s) = delete;
void *operator new[](size_t s) = delete;
private:
T *mptr;
};
int main()
{
// 裸指针
int *p = new int(10);
SmartPointer<int> ptr(new int(10));
*ptr = 20; // *ptr=20; => (operator*())=20;
class Test
{
public:
void test() { cout << "-----" << endl; }
};
SmartPointer<Test> t(new Test());
t->test(); // t->test() => (operator->())->test();
return 0;
}
所以这里就存在一个问题,能不能使用一个SmartPointer
来初始化一个智能指针,将对象存储在栈上?
结果肯定是不能的,上述代码尽管在语法上是没有问题的,但是智能指针利用的就是对象在栈上,出作用域进行析构,所以就会自动释放开辟的资源,现在使用一个new开辟智能指针的对象,这就是本末倒置。
不带引用计数的智能指针:只有一个智能指针能管理资源。
上述代码存在一个问题,就是当使用一个SmartPointer拷贝构造函数时,mptr会指向同一片内存,当两个对象开始析构时,会导致内存的重复释放。
SmartPointer<int> p1=SmartPointer(new int(10));
SmartPointer<int> p2(p1);//发生浅拷贝,p1在析构的时候,mptr已经被释放了。
对于这种普通的构造函数浅拷贝来说,直接重写拷贝构造函数就可以解决,在类的实现中添加:
SmartPointer(const SmartPointer<T> &src) { mptr = new T(*src.mptr); }
但是如果用户要的就是p1和p2就是需要指向同一块内存空间,那么就不能使用自定义拷贝构造函数,这样就曲解了智能指针的作用。
不带引用计数的智能指针有:auto_ptr
(C++库中包含的)、scope_ptr
和unique_ptr
(后面两个都是C++11添加的)。如何解决浅拷贝问题?
智能指针头文件在memory
中。
auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
_Ty* release() noexcept {
_Ty* _Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
_Ty* _Myptr;
使用auto_ptr
的拷贝构造函数时,会先将原来的地址先保存,然后断开原来的指针,再将当前的指针指向原来的地址。简而言之,就是把原来对象的资源赋给当前对象,原来对象的东西全部被释放了,只有最后的对象存在内容。
不推荐容器中使用auto_ptr,就会导致元素的释放。一个容器给另一个容器赋值,导致第一个容器中的值全部被释放了。
scope_ptr
内部删除了拷贝构造函数和赋值操作符重载函数。
scope_ptr(const scope_ptr<T> &)=delete;
scope_ptr<T>& operator=(const scope_ptr<T> &)=delete;
所以scope_ptr
资源一直存放再第一个对象上,防止资源被释放。
unique_ptr
和scope_ptr
都删除了拷贝构造函数和操作符重载函数,但是实现了右值引用参数的构造函数和赋值操作符重载函数。所以可以使用右值操作符重载函数和右值引用的拷贝构造函数进行unique_ptr
智能指针的使用。
unique_ptr(const scope_ptr<T> &)=delete;
unique_ptr(const scope_ptr<T> &&);
unique_ptr<T>& operator=(const unique_ptr<T> &)=delete;
unique_ptr<T>& operator=(const unique_ptr<T> &&);
但是unique_ptr
支持右值引用的实现,类似于:
unique_ptr<int> p1(new int(10));
//unique_ptr p2(p1);//不支持,删除了拷贝构造函数
unique_ptr<int> p2(std::move(p1));//强制将p1转化为右值,move函数的作用:将一个左值转化为右值
//但是这样也会将p1的资源转移到p2,所有后面不要继续访问p1,因为p1资源已经被释放了。
带引用计数的智能指针:多个智能指针可以管理同一个资源。给每一个对象资源匹配一个引用计数。
智能指针 -> 存在资源的使用 -> 引用计数+1。
智能指针 -> 不使用资源的时候(释放) -> 引用计数-1 -> 如果引用计数为0,就需要释放资源。
这里实现了一个自定义的带计数器的智能指针:
#include
#include
using namespace std;
template <typename T>
class RefCnt
{
public:
RefCnt(T* ptr = nullptr) : mptr(ptr)
{
if (mptr != nullptr)
{
mcount = 1;
}
}
void addRef()
{
++mcount;
}
int delRef()
{
mcount--;
return mcount;
}
private:
T* mptr;
int mcount;
};
template <typename T>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr) : mptr(ptr)
{
mpRefCnt = new RefCnt<T>(mptr);
}
~SmartPtr()
{
if (mpRefCnt->delRef() == 0)
{
delete mptr;
mptr = nullptr;
}
}
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
SmartPtr(const SmartPtr<T>& src) : mptr(src.mptr), mpRefCnt(src.mpRefCnt)
{
if (mptr != nullptr)
{
mpRefCnt->addRef();
}
}
// 将src的资源转到等式左边的对象上,所以右边的src对象如果引用计数为1那么就应该让资源释放
// 这里好像存在一个bug,当左值是一个mptr=nullptr时,那么本身引用计数就为0,所以不会触发第二个判断
// 本意应该是左值如果引用计数为1,那么就让其释放,可能没考虑到这种
SmartPtr<T>& operator=(const SmartPtr<T>& src)
{
if (this == &src)
{
return *this;
}
if (mpRefCnt->delRef() == 0)
{
delete mptr;
}
mptr = src.mptr;
mpRefCnt = src.mpRefCnt;
mpRefCnt->addRef();
return *this;
}
private:
T* mptr; // 指向资源的指针
RefCnt<T>* mpRefCnt; // 指向该资源引用计数对象的指针
};
int main()
{
SmartPtr<int> p1(new int(10));
SmartPtr<int> p2(p1);
cout << *p1 << "-------" << *p2 << endl;
SmartPtr<int> p3;
p3 = p1;
cout << *p3 << endl;
cout << *p1 << endl;
return 0;
}
上述智能指针在多线程环境中存在一个问题,就是引用计数的mcount
不是线程安全的,在真实环境中shared_ptr/weak_ptr
是线程安全的,其实也就是将int
改为atomic_int
类型,表示该计数器可是原子类型的,只能一个线程访问。
弱智能指针,不会改变资源的引用计数。是一种观察者模式,资源可能被释放了。所以在使用的时候需要将其转化为强智能指针。
强智能指针,会改变资源的引用计数。
强智能指针会存在循环引用(交叉引用)的问题。会出现什么样的结果?怎么解决?
交叉引用案例:
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb;
private:
};
class B {
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra;
};
int main()
{
//shared_ptr
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
// 添加之后就会出现问题,导致资源无法释放(因为添加指针的时候会出现引用计数+1的情况,
// 在结束作用域之后,其实引用计数为1,导致没有释放资源)
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
解决方法:定义对象的时候使用强智能指针,引用对象的地方使用弱智能指针。
交叉引用案例:
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb;
private:
};
class B {
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra;
};
int main()
{
//shared_ptr
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
// 添加之后就会出现问题,导致资源无法释放(因为添加指针的时候会出现引用计数+1的情况,
// 在结束作用域之后,其实引用计数为1,导致没有释放资源)
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
但是如果在类内部存在指针互相调用的情况,那么弱智能指针会存在无法调用函数的情况(弱智能指针是一个观察者角色,需要使用lock()方法转化为强智能指针),类似以下情况:
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void test()
{
cout << "A::test()" << endl;
}
weak_ptr<B> _ptrb;
private:
};
class B {
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void test()
{
//_ptra->test();// 无法直接调用A的函数,因为这个时候_ptra是弱智能指针,是一个观察者角色
shared_ptr<A> ps = _ptra.lock();
if (ps != nullptr)
{
ps->test();
}
}
weak_ptr<A> _ptra;
};
子线程在等待后,资源已经被释放了,导致无法调用函数完成,程序执行和实际不一致。如下示例所示:
#include
#include
#include
using namespace std;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void test() { cout << "A::test()" << endl; }
};
void handler01(A* p)
{
p->test();
}
void handler02(weak_ptr<A> q)
{
// 让子线程沉睡2s,沉睡2s之后,子线程可能已经被释放了
std::this_thread::sleep_for(chrono::seconds(2));
shared_ptr<A> ps = q.lock();
if (ps != nullptr)
{
ps->test();
}
else
{
cout << "q resource is deleted!" << endl;
}
}
int main()
{
A* p = new A();
thread th(handler01, p);
delete p;
th.detach();
{
shared_ptr<A> p(new A());
thread th(handler02, weak_ptr<A>(p));
th.detach();
}
std::this_thread::sleep_for(chrono::seconds(5));
return 0;
}
子线程中对象被释放了,导致无法调用test()
函数,所以没有正常打印结果。
智能指针的删除器 deletor
。
智能指针:能够保证资源绝对的释放,delete ptr; delete []ptr;
自定义指针删除器示例代码,作为第二个参数传入智能指针中:
template<typename T>
class MyDeletor
{
public:
void operator()(T* ptr) const
{
delete[]ptr;
}
};
template<typename T>
class MyFileDeletor
{
public:
void operator()(T* ptr) const
{
fclose(ptr);
}
};
int main()
{
unique_ptr<int, MyDeletor<int>> ptr1(new int[10]);
unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt","r"));
return 0;
}
但是也存在着一个问题,对于一个类型,可能需要自定义一种类型的删除器。
所以就可以使用lambda表达式联合使用:
unique_ptr<int, function<void(int*)>> ptr3(new int[10], [](int* p)->void {
delete[]p;
});
unique_ptr<FILE, function<void(FILE*)>> ptr4(fopen("data.txt", "r"), [](FILE* p)->void {
fclose(p);
});