智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存
c++11提供了四种智能指针
独占智能指针就是std::unique_ptr指针,它不允许其他智能指针共享其内部的指针,可以通过构造函数构造一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另外一个unique_ptr指针,内部是对赋值构造函数给为delete的
// 通过构造函数初始化对象
std::unique_ptr<int> ptr1(new int(10));
// error, 不允许将一个unique_ptr赋值给另一个unique_ptr
std::unique_ptr<int> ptr2 = ptr1;
1、将对象new出来
#include
#include
class Test{
public:
Test(int num)
:_num(num){}
void test(){
std::cout << _num << std::endl;
}
private:
int _num;
};
int main(){
std::unique_ptr<Test> ptr(new Test(10));
ptr->test();
return 0;
}
输出结果如下:
10
2、赋值方式,将该new出来的智能指针赋值给另外一个智能指针
#include
#include
class Test{
public:
Test(int num)
:_num(num){}
void test(){
std::cout << _num << std::endl;
}
private:
int _num;
};
int main(){
std::unique_ptr<Test> ptr(new Test(10));
std::unique_ptr<Test> ptr1;
ptr1 = ptr;
ptr->test();
return 0;
}
输出结果如下:
/root/test/main.cpp: 在函数‘int main()’中:
/root/test/main.cpp:18:10: 错误:使用了被删除的函数‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Test; _Dp = std::default_delete<Test>]’
ptr1 = ptr;
^
In file included from /usr/include/c++/4.8.2/memory:81:0,
from /root/test/main.cpp:2:
/usr/include/c++/4.8.2/bits/unique_ptr.h:274:19: 错误:在此声明
unique_ptr& operator=(const unique_ptr&) = delete;
从输出结果可以看出赋值构造函数被delete了,所以这就是std::unique_ptr不能被赋值的原因,因为它智能独占资源,别人不能和他共享
3、通过函数返回给其他的unique_ptr
#include
#include
class Test{
public:
Test(int num)
:_num(num){}
void test(){
std::cout << _num << std::endl;
}
private:
int _num;
};
std::unique_ptr<Test> func(){
return std::unique_ptr<Test>(new Test(10));
}
int main(){
std::unique_ptr<Test> ptr = func();
ptr->test();
return 0;
}
输出结果如下:
10
4、通过std::move转移给其他智能指针
#include
#include
class Test{
public:
Test(int num)
:_num(num){}
void test(){
std::cout << _num << std::endl;
}
private:
int _num;
};
int main(){
std::unique_ptr<Test> ptr(new Test(10));
std::unique_ptr<Test> ptr1;
ptr1 = std::move(ptr);
ptr1->test();
return 0;
}
输出结果如下:
10
使用c++11中的std::move就是发生管理权转移,将智能指针ptr的管理权转移给智能指针ptr1
注意:将智能指针ptr管理权转移给智能指针ptr1后,原始智能指针ptr就不可再使用,否则会发生段错误,因为ptr已经不再拥有该资源了,如果再次使用ptr,会找不到该资源的地址,导致发生段错误
函数原型如下:
void reset( pointer ptr = pointer() ) noexcept;
使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针
int main()
{
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = move(ptr1);
ptr1.reset();
ptr2.reset(new int(250));
return 0;
}
函数原型如下:
pointer get() const noexcept;
#include
#include
int main(){
std::unique_ptr<int> ptr1(new int(10));
std::unique_ptr<int> ptr2;
ptr2 = std::move(ptr1);
ptr2.reset(new int(666));
// 得到的结果是666
std::cout << *ptr2.get() << std::endl;
return 0;
}
共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr 是一个模板类,如果要进行初始化有三种方式:
共享智能指针初始化完了就指向管理的那段内存,如果想要获取当前有多少个共享智能指针同时指向这块内存,可以使用成员函数use_count
函数原型如下:
// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;
1、使用构造函数初始化智能指针
#include
#include
int main(){
std::shared_ptr<int> ptr(new int(100));
std::cout << "ptr管理内存引用计数: " << ptr.use_count() << std::endl;
std::shared_ptr<int> ptr1;
std::cout << "ptr1管理内存引用计数: " << ptr1.use_count() << std::endl;
std::shared_ptr<int> ptr2(nullptr);
std::cout << "ptr2管理内存引用计数: " << ptr2.use_count() << std::endl;
return 0;
}
输出结果如下:
ptr管理内存引用计数: 1
ptr1管理内存引用计数: 0
ptr2管理内存引用计数: 0
如果智能指针被初始化了一块有效内存,那么这块内存的引用计数 + 1,如果智能指针没有被初始化或者被初始化为 nullptr 空指针,引用计数不会 + 1。另外,不要使用一个原始指针初始化多个 shared_ptr。
例如:
int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); // error, 编译不会报错, 运行会出错
2、通过拷贝和移动构造函数初始化
当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被自动调用了
#include
#include
int main(){
std::shared_ptr<int> ptr(new int(100));
std::cout << "ptr管理内存引用计数: " << ptr.use_count() << std::endl;
std::shared_ptr<int> ptr1(ptr);
std::cout << "ptr1管理内存引用计数: " << ptr1.use_count() << std::endl;
std::shared_ptr<int> ptr2 = ptr;
std::cout << "ptr2管理内存引用计数: " << ptr2.use_count() << std::endl;
std::shared_ptr<int> ptr3(std::move(ptr1));
std::cout << "ptr3管理内存引用计数: " << ptr3.use_count() << std::endl;
std::shared_ptr<int> ptr4 = std::move(ptr2);
std::cout << "ptr4管理内存引用计数: " << ptr4.use_count() << std::endl;
return 0;
}
输出结果如下:
ptr管理内存引用计数: 1
ptr1管理内存引用计数: 2
ptr2管理内存引用计数: 3
ptr3管理内存引用计数: 3
ptr4管理内存引用计数: 3
如果采用拷贝的方式进行shared_ptr的初始化,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加,如果使用移动构造的方式初始智能指针对象,只是转让了内存的管理权,管理内存的对象并不会增加,因此内存的引用计数不会变化
3、通过std::make_shared进行初始化
通过 C++ 提供的 std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数原型如下:
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
具体使用示例
#include
#include
int main(){
std::shared_ptr<int> ptr = std::make_shared<int>(100);
return 0;
}
使用 std::make_shared() 模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数
弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 * 和 ->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在
#include
#include
int main(){
std::shared_ptr<int> ptr;
std::weak_ptr<int> ptr1;
std::weak_ptr<int> ptr2(ptr1);
std::weak_ptr<int> ptr3 = ptr1;
std::weak_ptr<int> ptr4(ptr);
return 0;
}
1、use_count()
通过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数,函数原型如下:
// 函数返回所监测的资源的引用计数
long int use_count() const noexcept;
#include
#include
int main(){
std::shared_ptr<int> ptr(new int(100));
std::weak_ptr<int> ptr1 = ptr;
std::cout << ptr1.use_count() << std::endl;
return 0;
}
输出结果如下:
1
通过上述结果可知,weak_ptr不对引用计数有任何改变
2、expired()
通过调用 std::weak_ptr 类提供的 expired() 方法来判断观测的资源是否已经被释放,函数原型如下:
// 返回true表示资源已经被释放, 返回false表示资源没有被释放
bool expired() const noexcept;
#include
#include
using namespace std;
int main()
{
shared_ptr<int> shared(new int(10));
weak_ptr<int> weak(shared);
cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
shared.reset();
cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
return 0;
}
输出结果如下:
1. weak is not expired
2. weak is expired
weak_ptr 监测的就是 shared_ptr 管理的资源,当共享智能指针调用 shared.reset(); 之后管理的资源被释放,因此 weak.expired() 函数的结果返回 true,表示监测的资源已经不存在了
对于shared_ptr的使用,存在一个循环引用的问题,循环引用会导致内存泄漏
#include
#include
using namespace std;
struct TA;
struct TB;
struct TA
{
shared_ptr<TB> bptr;
~TA()
{
cout << "class TA is disstruct ..." << endl;
}
};
struct TB
{
shared_ptr<TA> aptr;
~TB()
{
cout << "class TB is disstruct ..." << endl;
}
};
void testPtr()
{
shared_ptr<TA> ap(new TA);
shared_ptr<TB> bp(new TB);
cout << "TA object use_count: " << ap.use_count() << endl;
cout << "TB object use_count: " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "TA object use_count: " << ap.use_count() << endl;
cout << "TB object use_count: " << bp.use_count() << endl;
}
int main()
{
testPtr();
return 0;
}
输出结果如下:
TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 2
共享智能指针 ap、bp 对 TA、TB 实例对象的引用计数变为 2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 TA、TB 的实例对象不能被析构,最终造成内存泄露。通过使用 weak_ptr 可以解决这个问题,只要将类 TA 或者 TB 的任意一个成员改为 weak_ptr,修改之后的代码如下:
#include
#include
using namespace std;
struct TA;
struct TB;
struct TA
{
weak_ptr<TB> bptr;
~TA()
{
cout << "class TA is disstruct ..." << endl;
}
};
struct TB
{
shared_ptr<TA> aptr;
~TB()
{
cout << "class TB is disstruct ..." << endl;
}
};
void testPtr()
{
shared_ptr<TA> ap(new TA);
shared_ptr<TB> bp(new TB);
cout << "TA object use_count: " << ap.use_count() << endl;
cout << "TB object use_count: " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "TA object use_count: " << ap.use_count() << endl;
cout << "TB object use_count: " << bp.use_count() << endl;
}
int main()
{
testPtr();
return 0;
}
输出结果如下:
TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 1
class TB is disstruct ...
class TA is disstruct ...
通过输出的结果可以看到类 TA 或者 TB 的对象被成功析构了。
上面程序中,在对类 TA 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域之后 bp 的引用计数减为 0,类 TB 的实例对象被析构。
在类 TB 的实例对象被析构的时候,内部的 aptr 也被析构,其对 TA 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 TA 对象的管理也解除了,内存的引用计数减为 0,类 TA 的实例对象被析构。
参考:
爱编程的大丙