目录
C++ 不像 Java 自带垃圾回收机制,必须释放掉分配的内存,否则就会造成内存泄漏。因此 C++11 引入了智能指针。
定义:
智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在区域时,自动地销毁分配的对象,防止内存泄漏。
智能指针的核心实现技术是引用技术,每使用它一次,内部引用计数加 1,每析构一次内部引用计数减 1,减为 0 时,删除原始指针指向的堆区内存,使用智能指针需要引用头文件。
- std::shared_ptr 共享的智能指针
- std::weak_ptr 弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的,解决循环引用问题
- std::unique_ptr 独占的智能指针
一、shared_ptr(共享智能指针)
1.初始化
共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 share_ptr 是一个模板类,如果进行初始化,有三种方式:
- 通过构造函数初始化
- std::make_shared 辅助函数
- reset 方法
(1)use_count 成员函数
共享智能指针对象初始化完毕之后,就指向了要管理的那块堆区内存,如果想要查看当前有多少个智能指针同时管理着这块内存,可以使用共享智能指针提供的一个成员函数use_count。
(2)构造函数初始化
//使用智能指针管理一块 int 型的堆内存
shared_ptr<int> ptr1(new int(520));
cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl;
//使用智能指针管理一块 字符数组 对应的堆内存
shared_ptr<char> ptr2(new char[520]);
cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;
shared_ptr<int> ptr3;
cout << "ptr3管理的内存引用计数:" << ptr3.use_count() << endl;
//创建智能指针对象,初始化为空
shared_ptr<int> ptr4(nullptr);
cout << "ptr4管理的内存引用计数:" << ptr4.use_count() << endl;
//输出结果
/*
ptr1管理的内存引用计数:1
ptr2管理的内存引用计数:1
ptr3管理的内存引用计数:0
ptr4管理的内存引用计数:0
*/
如果智能指针被初始化了一块有效内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为 nullptr 空指针,引用计数为 0。另外,不要使用一个原始指针初始化多个 shared_ptr 。
(3)拷贝构造和移动构造函数初始化
当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被调用了。
//构造函数
cout << "构造函数" << endl;
shared_ptr<int> ptr1(new int(520));
cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl;
//拷贝构造函数
cout << "拷贝构造函数" << endl;
//ptr2拷贝构造ptr1,先让ptr1的引用计数+1,再让ptr2的引用计数等于ptr2的引用计数
shared_ptr<int> ptr2(ptr1);
cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;
shared_ptr<int> ptr3 = ptr1;
cout << "ptr3管理的内存引用计数:" << ptr3.use_count() << endl;
//移动构造函数
cout << "移动构造函数" << endl;
//把ptr1的指针给ptr4,再把ptr1的引用计数给ptr4,然后把ptr1的指针和引用计数置空
shared_ptr<int> ptr4(std::move(ptr1));
cout << "ptr4管理的内存引用计数:" << ptr4.use_count() << endl;
cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl;
shared_ptr<int> ptr5 = move(ptr2);
cout << "ptr5管理的内存引用计数:" << ptr5.use_count() << endl;
cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;
//输出结构
*/
构造函数
ptr1管理的内存引用计数:1
拷贝构造函数
ptr2管理的内存引用计数:2
ptr3管理的内存引用计数:3
移动构造函数
ptr4管理的内存引用计数:3
ptr1管理的内存引用计数:0
ptr5管理的内存引用计数:3
ptr2管理的内存引用计数:0
*/
如果使用拷贝的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用计数也会增加。如果使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用计数不会增加。
(4)std::make_shared 初始化
通过 C++11 提供的 std::make_shared() 就可以完成内存对象的创建,并将其初始化给智能指针。
#include<iostream>
using namespace std;
#include<memory>
#include<string>
class Test
{
public:
Test()
{
cout << "无参构造函数" << endl;
}
Test(int x)
{
cout << "int类型构造函数" << endl;
}
Test(string str)
{
cout << "string类型的构造函数" << endl;
}
~Test()
{
cout << "析构函数" << endl;
}
};
int main()
{
//使用智能指针管理一块 int 型的堆内存,内部引用计数为1
shared_ptr<int> ptr1 = make_shared<int>(520);
cout << "ptr2管理的内存引用计数:" << ptr1.use_count() << endl;
//调用无参构造函数
shared_ptr<Test> ptr2 = make_shared<Test>();
cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;
//调用int类型构造函数
shared_ptr<Test> ptr3 = make_shared<Test>(520);
cout << "ptr3管理的内存引用计数:" << ptr3.use_count() << endl;
//调用string类型构造函数
shared_ptr<Test> ptr4 = make_shared<Test>("QQQQQ");
cout << "ptr4管理的内存引用计数:" << ptr4.use_count() << endl;
return 0;
}
//输出
/*
ptr2管理的内存引用计数:1
无参构造函数
ptr2管理的内存引用计数:1
int类型构造函数
ptr3管理的内存引用计数:1
string类型的构造函数
ptr4管理的内存引用计数:1
析构函数
析构函数
析构函数
*/
(5)reset方法初始化
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
shared_ptr<int> ptr1 = make_shared<int>(520);
shared_ptr<int> ptr2 = ptr1;
shared_ptr<int> ptr3 = ptr1;
shared_ptr<int> ptr4 = ptr1;
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
ptr4.reset();
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
shared_ptr<int> ptr5;
ptr5.reset(new int(250));
cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
return 0;
}
//打印结果如下:
/*
ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4
ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0
ptr5管理的内存引用计数: 1
*/
对于一个未初始化的共享智能指针,可以通过 reset 方法来初始化,当智能指针中有值的时候,调用 reset 会使引用计数减一。
2.获取原始指针
get() 函数
int main()
{
shared_ptr<int> p(new int);
*p = 100;
cout << *p.get() << " " << *p << endl;
return 0;
}
//100 100
二、weak_ptr(弱引用智能指针)
弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 * 和 ->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在。
1.初始化
int main()
{
shared_ptr<int> sp(new int);
//构造了一个空的 weak_ptr对象
weak_ptr<int> wp1;
//通过一个空weak_ptr对象构造了另一个空weak_ptr对象
weak_ptr<int> wp2(wp1);
//通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
weak_ptr<int> wp3(sp);
//通过一个share_ptr对象构造了一个可用的weak_ptr实例对象(这是一个隐式类型转换)
weak_ptr<int> wp4;
wp4 = sp;
//通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象
weak_ptr<int> wp5;
wp5 = wp3;
return 0;
}
2.use_count()
通过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sp(new int);
weak_ptr<int> wp1;
weak_ptr<int> wp2(wp1);
weak_ptr<int> wp3(sp);
weak_ptr<int> wp4;
wp4 = sp;
weak_ptr<int> wp5;
wp5 = wp3;
cout << "use_count: " << endl;
cout << "wp1: " << wp1.use_count() << endl;
cout << "wp2: " << wp2.use_count() << endl;
cout << "wp3: " << wp3.use_count() << endl;
cout << "wp4: " << wp4.use_count() << endl;
cout << "wp5: " << wp5.use_count() << endl;
return 0;
}
//输出:
/*
use_count:
wp1: 0
wp2: 0
wp3: 1
wp4: 1
wp5: 1
*/
通过打印的结果可以知道,虽然弱引用智能指针 wp3、wp4、wp5 监测的资源是同一个,但是它的引用计数并没有发生任何变化,也进一步证明了 weak_ptr 只是监测资源,并不管理资源。
3.expired()
通过调用 std::weak_ptr 类提供的 expired() 方法来判断观测的资源是否已经被释放。
#include <iostream>
#include <memory>
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,表示监测的资源已经不存在了。
4.lock()
通过调用 std::weak_ptr 类提供的 lock() 方法来获取管理所监测资源的 shared_ptr 对象。
#include<iostream>
using namespace std;
#include<memory>
#include<string>
int main()
{
shared_ptr<int> sp1, sp2;
weak_ptr<int> wp;
sp1 = make_shared<int>(520);//用make_shared初始化shared_ptr
wp = sp1;//wp监测sp1里面的资源(指针、引用计数)
sp2 = wp.lock();//通过调用lock方法得到一个用于管理weak_ptr对象所监测的资源的共享智能对象指针,使用这个对象初始化sp2,此时所监测资源的引用计数为2
cout << "use_count:" << wp.use_count() << endl;
sp1.reset();//共享智能指针sp1被重置,weak_ptr对象所监测的资源的引用计数减一
cout << "use_count:" << wp.use_count() << endl;
sp1 = wp.lock();//sp1被重新初始化,并且管理的还是weak_ptr对象所监测的资源,引用计数加1
cout << "use_count:" << wp.use_count() << endl;
//共享智能指针对象sp1和sp2管理的是同一块内存,最终打印的结果是相同的,都是520
cout << "*sp1 = " << *sp1 << endl;
cout << "*sp2 = " << *sp2 << endl;
return 0;
}
//输出
/*
use_count:2
use_count:1
use_count:2
*sp1 = 520
*sp2 = 520
*/
5.reset()
通过调用 std::weak_ptr 类提供的 reset() 方法来情况对象,使其不监测任何资源。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;
wp.reset();
cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;
return 0;
}
//输出
/*
1. wp is not expired
2. wp is expired
*/
6.管理返回this的shared_ptr
#include<iostream>
using namespace std;
#include<memory>
#include<string>
struct Test
{
shared_ptr<Test> getSharedPtr()
{
return shared_ptr<Test>(this);
}
~Test()
{
cout << "析构函数" << endl;
}
};
int main()
{
shared_ptr<Test> sp1(new Test);
cout << "引用计数:" << sp1.use_count() << endl;
shared_ptr<Test> sp2 = sp1->getSharedPtr();
cout << "引用计数:" << sp1.use_count() << endl;
return 0;
}
//输出
/*
引用计数:1
引用计数:1
析构函数
析构函数
*/
通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针 this 构造了两个智能指针对象 sp1 和sp2,这两者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象。在离开作用域之后,this 将被构造的两个智能指针各自析构,导致重复析构的错误。
#include <iostream>
#include <memory>
using namespace std;
struct Test : public enable_shared_from_this<Test>
{
shared_ptr<Test> getSharedPtr()
{
return shared_from_this();
}
~Test()
{
cout << "析构函数" << endl;
}
};
int main()
{
shared_ptr<Test> sp1(new Test);
cout << "引用个数: " << sp1.use_count() << endl;
shared_ptr<Test> sp2 = sp1;
cout << "引用个数: " << sp1.use_count() << endl;
return 0;
}
//输出
/*
引用个数: 1
引用个数: 2
析构函数
*/
7.循环引用问题
#include<iostream>
using namespace std;
#include<memory>
#include<string>
class A;
class B;
class A
{
public:
shared_ptr<B> bptr;
~A()
{
cout << "class TA is disstruct ..." << endl;
}
};
class B
{
public:
shared_ptr<A> aptr;
~B()
{
cout << "class TB is disstruct ..." << endl;
}
};
void testPtr()
{
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B);
cout << "A的引用计数:" << ap.use_count() << endl;
cout << "B的引用计数:" << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "A的引用计数:" << ap.use_count() << endl;
cout << "B的引用计数:" << bp.use_count() << endl;
}
int main()
{
testPtr();
return 0;
}
//输出
/*
A的引用计数:1
B的引用计数:1
A的引用计数:2
B的引用计数:2
*/
在这段代码中,我们定义了两个类 A 和 B,它们之间相互包含对方的 shared_ptr。这样的情况下,当这两个 shared_ptr 交叉引用时,会导致引用计数不会降为零,从而导致内存泄漏。
让我们来分析一下 testPtr 函数的执行过程:
- 在 testPtr 函数中,首先创建了一个名为 ap 的 shared_ptr 智能指针,指向一个 A 类对象,并输出 ap 的引用计数(初始引用计数应该为1)。
- 然后,创建了一个名为 bp 的 shared_ptr 智能指针,指向一个 B 类对象,并输出 bp 的引用计数(初始引用计数应该为1)。
- 接着,ap->bptr = bp; 将 bp 赋值给 ap 中的 bptr,并且 bp->aptr = ap; 将 ap 赋值给 bp 中的 aptr,这样两个智能指针就相互交叉引用了,形成了循环引用。
- 最后,输出 ap 和 bp 的引用计数,你会发现它们的引用计数仍然是1,没有减少为0,因为它们之间的循环引用导致了内存泄漏。
这段代码中的循环引用会导致 A 和 B 对象永远无法被正确地释放,因为它们的引用计数永远不会降为零。这就是为什么在 C++ 中要避免循环引用的原因。
为了解决这个问题,你可以使用 weak_ptr 来打破 shared_ptr 的循环引用。weak_ptr 允许共享资源但不增加引用计数,因此当 shared_ptr 的引用计数归零时,weak_ptr 就不会阻止资源的释放。
共享智能指针 ap、bp 对 A、B 实例对象的引用计数变为 2,在共享智能指针离开作用域之后,引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 A、B 的实例对象不能被析构,最终造成内存泄漏。通过使用 weak_ptr 可以解决这个问题,只需要将类 A 或者 B 的任意一个成员改为 weak_ptr。
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A
{
public:
weak_ptr<B> bptr;
~A()
{
cout << "A 的 析构函数" << endl;
}
};
class B
{
public:
shared_ptr<A> aptr;
~B()
{
cout << "B 的 析构函数" << endl;
}
};
void testPtr()
{
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B);
cout << "A 的 引用计数: " << ap.use_count() << endl;
cout << "B 的 引用计数: " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "A 的 引用计数: " << ap.use_count() << endl;
cout << "B 的 引用计数: " << bp.use_count() << endl;
}
int main()
{
testPtr();
return 0;
}
//输出
/*
A 的 引用计数: 1
B 的 引用计数: 1
A 的 引用计数: 2
B 的 引用计数: 1
B 的 析构函数
A 的 析构函数
*/
在上面程序中,在对类 A 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域后,bp 的引用计数减为 0,类 B 的实例对象被析构。
在类 B 的实例对象被析构的时候,内部的 aptr 也被析构,其对 A 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 A 对象的管理也解除了,内存的引用计数减为 0,类 A 的实例对象被析构。
三、unique_ptr(独占智能指针)
1.初始化
std::unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个 unique_ptr 赋值给另一个 unique_ptr。
#include
#include
#include
using namespace std;
int main()
{
// 通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
// 报错
unique_ptr<int> ptr2 = ptr1;
return 0;
}
unique_ptr 不允许被复制,但是可以通过函数返回给其他的 unique_ptr,还可以通过 move() 转移给其他的 unique_ptr。还是一个 unique_ptr 独占一个地址。
2.reset()
使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 ;
ptr1.reset(); //解除对原始内存的管理
ptr2.reset(new int(250)); //重新指定智能指针管理的原始内存
return 0;
}
3.get()
如果想要获取独占智能指针管理的原始地址,可以调用 get() 方法。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = move(ptr1);
ptr2.reset(new int(250));
cout << *ptr2.get() << endl; // 得到内存地址中存储的实际数值 250
return 0;
}
//输出
250