智能指针
std智能指针
C++中使用智能指针时需要包含memory头文件,智能指针的本质是使用栈上的智能指针对象来管理堆上申请的内存空间,当栈上的智能指针对象销毁时,管理的堆上的对象也会随之释放,不需要手动delete。
auto_ptr
C++11已经不使用auto_ptr,已经被unique_ptr所取代
auto_ptr<int> p1(new int(1));
auto_ptr<int> p2;
p2 = p1;
编译可以通过,但此时使用p1会报错,因为p2已经剥夺了p1的使用权力。
unique_ptr
unique_ptr能够保证同一时间内只有一个智能指针指向一个对象。
unique_ptr<int> p1(new int(1));
unique_ptr<int> p2;
p2 = p1; //报错
上面使用p2=p1时编译会报错,而auto_ptr能通过编译期。因此,unique_ptr比auto_ptr更安全。
unique_ptr<int> p1(new int(1));
unique_ptr<int> p2;
p2 = p1;//错误
unique_ptr<int> p3;
p3 = unique_ptr<int>(new int(2));//正确
如果将一个右值赋值给unique_ptr对象,则可以编译通过,如果想使p2=p1通过编译,则可以使用move将左值转换为右值
unique_ptr<int> p1(new int(1));
unique_ptr<int> p2;
p2 = move(p1);//通过编译
内部原理:移动构造函数和右值赋值重载运算符,私有的拷贝构造和赋值重载运算符
unique_ptr(unique_ptr&& _Right)
unique_ptr& operator=(unique_ptr&& _Right)
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
shared_ptr
share_ptr也是使用最多的智能指针对象,它允许多个智能指针指向同一个对象,采用引用计数的方式,当引用计数为0时,会释放所指向的对象。
成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如
shared_ptr<int> sp(new int(1));
cout << *sp << endl;
cout << *sp.get() << endl;
结果:
int* i1 = new int(1);
shared_ptr<int> ps1(i1);
shared_ptr<int> ps2;
ps2 = ps1;
cout << ps1.use_count() << endl;
cout << ps2.use_count() << endl;
cout << ps1.unique() << endl;
int* i3 = new int(2);
shared_ptr<int> ps3(i3);
cout << (ps1.get()) << endl;
cout << ps3.get() << endl;
swap(ps1, ps3);
cout << (ps1.get()) << endl;
cout << ps3.get() << endl;
cout << ps1.use_count() << endl;
cout << ps2.use_count() << endl;
ps2 = ps1;
cout << ps1.use_count() << endl;
cout << ps2.use_count() << endl;
ps1.reset();
cout << ps1.use_count() << endl;
cout << ps2.use_count() << endl;
结果:
weak_ptr
据我所知,weak_ptr使用较少,但这里还是介绍一下,他一般和share_ptr搭配起来使用,share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
weak_ptr使用时不会增加或减少对象的引用计数,他常用来解决shared_ptr相互引用造成的死锁问题,下面通过例子来看死锁问题
class B; //声明
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout << "A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout << "B delete\n";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
cout << pb.use_count() << endl; //1
cout << pa.use_count() << endl; //1
pb->pa_ = pa;
pa->pb_ = pb;
cout << pb.use_count() << endl; //2
cout << pa.use_count() << endl; //2
}
int main()
{
fun();
return 0;
}
上述代码的内存模型
可以看到堆上的两个对象相互引用,导致引用计数永远不可能为0,因此不可能释放对象。
如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_,改为weak_ptr pb_ ,运行结果如下:
这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。
注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:
shared_ptr<B> p = pa->pb_.lock();
p->print();
weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.
expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
use_count 返回与 shared_ptr 共享的对象的引用计数.
reset 将 weak_ptr 置空.
weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.
share_ptr和weak_ptr的核心实现
weak_ptr作为弱引用指针,其实现依赖于counter的计数器类和share_ptr的赋值,构造,所以先把counter和share_ptr简单实现
Counter简单实现
class Counter
{
public:
Counter() : s(0), w(0) {};
int s; //share_ptr的引用计数
int w; //weak_ptr的引用计数
};
counter对象的目地就是用来申请一个块内存来存引用基数,s是share_ptr的引用计数,w是weak_ptr的引用计数,当w为0时,删除Counter对象。
share_ptr的简单实现
template <class T>
class WeakPtr; //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用
template <class T>
class SharePtr
{
public:
SharePtr(T *p = 0) : _ptr(p)
{
cnt = new Counter();
if (p)
cnt->s = 1;
cout << "in construct " << cnt->s << endl;
}
~SharePtr()
{
release();
}
SharePtr(SharePtr<T> const &s)
{
cout << "in copy con" << endl;
_ptr = s._ptr;
(s.cnt)->s++;
cout << "copy construct" << (s.cnt)->s << endl;
cnt = s.cnt;
}
SharePtr(WeakPtr<T> const &w) //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用
{
cout << "in w copy con " << endl;
_ptr = w._ptr;
(w.cnt)->s++;
cout << "copy w construct" << (w.cnt)->s << endl;
cnt = w.cnt;
}
SharePtr<T> &operator=(SharePtr<T> &s)
{
if (this != &s)
{
release();
(s.cnt)->s++;
cout << "assign construct " << (s.cnt)->s << endl;
cnt = s.cnt;
_ptr = s._ptr;
}
return *this;
}
T &operator*()
{
return *_ptr;
}
T *operator->()
{
return _ptr;
}
friend class WeakPtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值
protected:
void release()
{
cnt->s--;
cout << "release " << cnt->s << endl;
if (cnt->s < 1)
{
delete _ptr;
if (cnt->w < 1)
{
delete cnt;
cnt = NULL;
}
}
}
private:
T *_ptr;
Counter *cnt;
};
share_ptr的给出的函数接口为:构造,拷贝构造,赋值,解引用,通过release来在引用计数为0的时候删除_ptr和cnt的内存。
weak_ptr简单实现
template <class T>
class WeakPtr
{
public: //给出默认构造和拷贝构造,其中拷贝构造不能有从原始指针进行构造
WeakPtr()
{
_ptr = 0;
cnt = 0;
}
WeakPtr(SharePtr<T> &s) : _ptr(s._ptr), cnt(s.cnt)
{
cout << "w con s" << endl;
cnt->w++;
}
WeakPtr(WeakPtr<T> &w) : _ptr(w._ptr), cnt(w.cnt)
{
cnt->w++;
}
~WeakPtr()
{
release();
}
WeakPtr<T> &operator=(WeakPtr<T> &w)
{
if (this != &w)
{
release();
cnt = w.cnt;
cnt->w++;
_ptr = w._ptr;
}
return *this;
}
WeakPtr<T> &operator=(SharePtr<T> &s)
{
cout << "w = s" << endl;
release();
cnt = s.cnt;
cnt->w++;
_ptr = s._ptr;
return *this;
}
SharePtr<T> lock()
{
return SharePtr<T>(*this);
}
bool expired()
{
if (cnt)
{
if (cnt->s > 0)
{
cout << "empty" << cnt->s << endl;
return false;
}
}
return true;
}
friend class SharePtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值
protected:
void release()
{
if (cnt)
{
cnt->w--;
cout << "weakptr release" << cnt->w << endl;
if (cnt->w < 1 && cnt->s < 1)
{
//delete cnt;
cnt = NULL;
}
}
}
private:
T *_ptr;
Counter *cnt;
};
weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。
QT智能指针
Qt中的智能指针包括:QSharedPointer、QScopedPointer、QScopedArrayPointer、QWeakPointer、QPointer、QSharedDataPointer
QSharedPointer
QSharedPointer类似于shared_ptr,使用该智能指针必须包含头文件
#include <QSharedPointer>
QSharedPointer 是线程安全的,因此即使有多个线程同时修改 QSharedPointer 对象也不需要加锁。这里要特别说明一下,虽然 QSharedPointer 是线程安全的,但是 QSharedPointer 指向的内存区域可不一定是线程安全的。所以多个线程同时修改 QSharedPointer 指向的数据时还要应该考虑加锁的。
QSharedPointer<int> sp(new int(1));
qDebug()<<*sp;
QScopedPointer
QScopedPointer类似于unique_ptr
QScopedArrayPointer
如果我们指向的内存数据是一个数组,这时可以用 QScopedArrayPointer。QScopedArrayPointer 与 QScopedPointer 类似,用于简单的场景。
QScopedArrayPointer<int>sp2(new int[10]);
sp2[1]=20;
当sp2被释放时,使用delete[]释放堆上的数组。
QWeakPointer
类似于weak_ptr
QPointer
QPointer 与其他的智能指针有很大的不同。其他的智能指针都是为了自动释放内存资源而设计的。 QPointer 智能用于指向 QObject 及派生类的对象。当一个 QObject 或派生类对象被删除后,QPointer 能自动把其内部的指针设为 0。这样我们在使用这个 QPointer 之前就可以判断一下是否有效了。
为什么非要是 QObject 或派生类呢,因为 QObject 可以构成一个对象树,当这颗对象树的顶层对象被删除时,它的子对象自动的被删除。所以一个 QObject 对象是否还存在,有时并不是那么的明显。有了 QPointer 我们在使用一个对象之前,至少可以判断一下。
要特别注意的是,当一个 QPointer 对象超出作用域时,并不会删除它指向的内存对象。这和其他的智能指针是不同的。
QSharedDataPointer
QSharedDataPointer 这个类是帮我们实现数据的隐式共享的。我们知道 Qt 中大量的采用了隐式共享和写时拷贝技术。比如下面这个例子:
QString str1 ="hello world";
QString str2 = str1;
str2[3]="G";
第二行执行完后,str2 和 str1 指向的同一片内存数据。当第三句执行时,Qt 会为 str2 的内部数据重新分配内存。这样做的好处是可以有效的减少大片数据拷贝的次数,提高程序的运行效率。
Qt 中隐式共享和写时拷贝就是利用 QSharedDataPointer 和 QSharedData 这两个类来实现的。