这个指针近乎完美,原来出现在boost库中,c 11时引入了标准库,解决了auto_ptr
对内部对象独占的机制,转而采用引用计数的方式,每增加一次赋值,则引用计数加1,每析构一个智能指针对象,则引用计数减1,当引用计数为1时销毁智能指针对象的同时,也析构内部对象。这种采用引用计数方式避免了对象所有权转移,所以作为函数返回值,函数参数,容器的元素都不会有问题,但是因为引用计数的加入,相应的会带来对引用计数维护的开销。
与auto_ptr
一样,shared_ptr
本身也是一个模板类,那么一般情况下直接用它来定义一个智能指针的对象,例如std::shared_ptr
需要注意的是pa
虽然叫智能指针,但是它是一个对象,在它的内部保存着一个原始的对象的指针。查看shared_ptr
的代码时发现,它主要有get
、swap
、reset
、unique
、use_count
、operator bool
、operator*
、operator->
、operator=
几个函数,与auto_ptr
相比少了release
函数,但是多了swap
、unique
、use_count
、operator bool
四个函数,下面通过一些例子来了解一下shared_ptr
的具体用法。
- vs2015 windows7(应该是c 11标准)
- 头文件
#include
- 命名空间
using namespace std;
首先我们先编写一些测试类,用来测试智能指针各个函数的作用,以及可能出现的问题,测试类的代码如下:
class example
{
public:
example(int param = 0)
{
number = param;
cout << "example: " << number << endl;
}
~example() { cout << "~example: " << number << endl; }
void test_print() { cout << "in test print: number = " << number << endl; }
void set_number(int num) { number = num; }
int get_number() { return number; }
private:
int number;
};
-
测试函数
get reset operator* operator->
这几个函数与auto_ptr
智能指针的用法一样,可以参考auto_ptr用法,get
函数可以获得智能指针包装的原始指针,可以用来判断被包装对象的有效性,也可以用来访问被包装对象,operator*
可以直接对智能指针包装的原始指针解引用,获得被包装的对象,operator->
用来取得原始对象的指针,引用成员时与get
函数作用相同,reset
函数用于重新设置内部对象,若参数为空,则表示取消对内部对象的引用,此时若引用计数大于1则进行减1操作,否则直接析构内部对象。需要注意的是普通的对象指针是无法隐式转换成shared_ptr
的,需要利用构造函数实现,示例代码如下:void test1() { //error c2440: “初始化”: 无法从“example *”转换为“std::shared_ptr
” //shared_ptr ptr1 = new example(1); shared_ptr ptr1(new example(1)); // example: 1(输出内容) if (ptr1.get()) // 调用函数get,获取原始指针,判断有效性 { cout << "ptr1 is valid" << endl; // 原始指针有效 } ptr1->test_print(); // in test print: number = 1(输出内容),调用operator-> ptr1.reset(); // ~example: 1(输出内容),调用函数reset,设置为空,释放原内部对象 ptr1.reset(new example(2)); // example: 2(输出内容),重新申请对象并设置 (*ptr1).test_print(); // in test print: number = 1(输出内容),调用operator* } // ~example: 1(输出内容),出定义域,释放内部对象 -
测试函数
operator bool
用法
operator bool
函数其实就是用来判断内部对象是否有效的,若内部对象不为空则返回true,否则返回false,大概的实现就是return this->get() != nullptr;
,测试代码如下:void test2() { shared_ptr
ptr2(new example(2)); // example: 2(输出内容) if (ptr2) // 调用operator bool cout << "ptr2 is valid" << endl; // ptr2 is valid(输出内容),说明ptr2是有效的 ptr2.reset(); // ~example: 2(输出内容),设置内部对象为空 if (ptr2) // 调用operator bool cout << "ptr2 is valid" << endl; // 没有输出,说明ptr2已经无效 } -
测试函数
swap
用法
从这个名字就可以看出,这个函数用于交换,那么是用来交换什么的呢?实际上是用来交换内部对象的,看下面的例子一试便知,代码运行过后,通过打印可以发现智能指针对象ptr3和ptr4的内部对象进行了交换:void test3() { shared_ptr
ptr3(new example(3)); // example: 3(输出内容) shared_ptr ptr4(new example(4)); // example: 4(输出内容) ptr3->test_print(); // in test print: number = 3(输出内容) ptr4->test_print(); // in test print: number = 4(输出内容) ptr3.swap(ptr4); // 调用函数swap ptr3->test_print(); // in test print: number = 4(输出内容) ptr4->test_print(); // in test print: number = 3(输出内容) } // ~example: 3(输出内容),出定义域,释放内部对象 // ~example: 4(输出内容),出定义域,释放内部对象 -
测试函数
unique use_count operator=
用法
为什么把这几个函数放到一起来说,因为他们是息息相关的,首先函数operator=
是用来处理赋值操作的,而赋值操作就会影响引用计数的变化,也就是赋值操作后,use_count
函数查询到的引用计数会发生变化,而当use_count
返回引用计数是1时,用来表明是否独自引用内部对象的函数unique
也会返回true,换句话说unique
函数的实现基本就是return this->use_count() == 1
,测试代码如下:void test4() { shared_ptr
ptr4(new example(4)); // example: 4(输出内容) if (ptr4.unique()) { cout << "ptr4 is unique" << endl; // ptr4 is unique(输出内容) cout << "ptr4 use count : " << ptr4.use_count() << endl;// ptr4 use count : 1(输出内容) } shared_ptr ptr5 = ptr4; if (ptr4) cout << "ptr4 is valid" << endl;// ptr4 is valid(输出内容)说明赋值之后两个智能指针对象都有效 if (ptr5) cout << "ptr5 is valid" << endl;// ptr5 is valid(输出内容)说明赋值之后两个智能指针对象都有效 if (ptr4.unique()) cout << "ptr4 is unique" << endl; // 没有输出,说明ptr4不是唯一管理内部对象的智能指针了 cout << "ptr4 use count : " << ptr4.use_count() << endl; // ptr4 use count : 2(输出内容) cout << "ptr5 use count : " << ptr5.use_count() << endl; // ptr4 use count : 2(输出内容) } // ~example: 4(输出内容),出定义域,释放内部对象 -
测试用同一个对象指针生成两个
shared_ptr
对象
与auto_ptr
一样,我测试的结果是崩溃,官方标准网站上说是结果未定义,基本上就是说不靠谱,别这样干,仔细想想也能理解,虽说shared_ptr
是通过引用计数方式实现,但也不是无所不能,比如这种情况,两个对象都是通过构造生成的,对内部对象的指针p
都是“唯一”引用的,也就是两个对象的内部引用计数都是1,当第一个智能指针对象销毁时,会析构内部对象,当第二个智能指针对象销毁时,同样会析构内部对象,这样就造成了崩溃,测试如下:void test5() { example *p = new example(5); // example: 5(输出内容) shared_ptr
ptr5(p); shared_ptr ptr6(p); cout << "ptr4 use count : " << ptr5.use_count() << endl;// ptr4 use count : 1(输出内容) cout << "ptr5 use count : " << ptr6.use_count() << endl;// ptr5 use count : 1(输出内容) } // ~example: 3(输出内容),出定义域,ptr5释放内部对象 // ~example : -572662307(输出内容),出定义域,ptr6释放内部对象,程序崩溃 -
测试
shared_ptr
作为函数参数和返回值
因为shared_ptr
内部是引用计数,而不是独占所有权,所以在赋值的时候只改变引用计数,不会发生所有权转移,所以这两种用法基本没有问题,发生在auto_ptr
上的崩溃惨剧也不会在这里上演,测试代码如下:void test6_inner1(shared_ptr
ptr6_1) { ptr6_1->test_print(); // in test print: number = 6(输出内容) cout << "ptr6_1 use count : " << ptr6_1.use_count() << endl;// ptr6 use count : 2(输出内容) } shared_ptr test6_inner2() { shared_ptr ptr6_2(new example(62)); // example:62(输出内容) ptr6_2->test_print(); // in test print: number = 62(输出内容) cout << "ptr6_2 use count : " << ptr6_2.use_count() << endl;// ptr6_2 use count : 1(输出内容) return ptr6_2; } void test6() { shared_ptr ptr6(new example(6)); // example:6(输出内容) ptr6->test_print(); // in test print: number = 6(输出内容) cout << "ptr6 use count : " << ptr6.use_count() << endl;// ptr6 use count : 1(输出内容) test6_inner1(ptr6); cout << "ptr6 use count : " << ptr6.use_count() << endl;// ptr6 use count : 1(输出内容) ptr6 = test6_inner2(); // ~example: 6(输出内容),ptr6接管新的对象,原来对象被析构 cout << "ptr6 use count : " << ptr6.use_count() << endl;// ptr6 use count : 1(输出内容) } // ~example: 62(输出内容),出定义域,ptr6释放内部对象 -
测试
shared_ptr
作为容器元素
在这里也不存在auto_ptr
作为容器元素时的争议,同样是引用计数的机制发挥了作用,使得他满足的容器的要求——其元素对象的拷贝与原对象相同或者等价,所以这里也不会出现问题,同时那些针对于容器的算法在shared_ptr
上也可以大显身手,比如下面这个排序的例子:// 一般会写成只读引用类型,这里为了说明问题才这样定义 bool comp(shared_ptr
a, shared_ptr b) { return a->get_number() > b->get_number(); } void test7() { vector > v(10); for (int i = 0; i < 10; i ) { v[i] = shared_ptr (new example(70 i)); }// 依次输出example:70,example:71,example:72...example:79 // 循环调用 for (int i = 0; i < 10; i ) { v[i]->test_print(); }// 依次输出in test print: number = 70....in test print: number = 79 sort(v.begin(), v.end(), comp); // 这可以正常运行,但是使用auto_ptr会死的很难看 // 循环调用 for (int i = 0; i < 10; i ) { v[i]->test_print(); }// 依次输出in test print: number = 79....in test print: number = 70 }// 依次输出~example: 79,~example: 78...~example: 70 -
测试使用指针或者引用作为参数
虽然shared_ptr
作为参数、返回值、容器元素貌似没有丝毫问题了,但是有时还是使用shared_ptr
对象的指针或者引用比较好,因为这样可以减少对对象的拷贝,毕竟对象的拷贝是需要消耗时间的,用更好的方式为什么不用呢,参考下面的用法,没有任何问题:void test8_inner1(shared_ptr
* ptr8_1) { (*ptr8_1)->test_print(); // in test print: number = 8(输出内容) cout << "ptr8_1 use count : " << (*ptr8_1).use_count() << endl;// ptr8_1 use count : 1(输出内容) } void test8_inner2(shared_ptr & ptr8_2) { ptr8_2->test_print(); // in test print: number = 8(输出内容) cout << "ptr8_2 use count : " << ptr8_2.use_count() << endl;// ptr8_2 use count : 1(输出内容) } void test8() { shared_ptr ptr8(new example(8)); // example:8(输出内容) ptr8->test_print(); // in test print: number = 8(输出内容) cout << "ptr8 use count : " << ptr8.use_count() << endl;// ptr8 use count : 1(输出内容) test8_inner1(&ptr8); cout << "ptr8 use count : " << ptr8.use_count() << endl;// ptr8 use count : 1(输出内容) test8_inner2(ptr8); cout << "ptr8 use count : " << ptr8.use_count() << endl;// ptr8 use count : 1(输出内容) } // ~example: 8(输出内容),出定义域,ptr8释放内部对像
shared_ptr
与auto_ptr
相比要优秀的多,这得益于其内部引用计数的实现,正是这种非独占所有权的方式,使其摆脱了auto_ptr
的种种限制,并将其踢出了c 标准(auto_ptr
在c 17中被移除),但是shared_ptr
也不是完美无缺的,引用计数不能解决所的问题,并且可能会带来一些问题,比如“循环引用问题”,这个得靠后面我们即将说到的weak_ptr
来解决,所以说没有什么结构是完美的,选择合适的就是最好的,综合前面多个测试的例子,可以得到一些经验。
shared_ptr
作为目前最优秀的指针,取代auto_ptr
是必然的,所以能使用shared_ptr
的地方还是尽量使用shared_ptr
。- 不要使用同一个原始对象的指针生成多个
shared_ptr
对象,这样使用会导致未定义的行为,比如test5
这个函数就导致了崩溃和错误的输出。 shared_ptr
不是万能的,如果不加思考的把原始指针都替换成shared_ptr
,虽然大部分能防止内存泄露,但是还会造成其他的问题,比如循环引用,这种情况需要使用weak_ptr
来解决问题,如果不解决就会造成另一种形式的内存泄漏。- 不要使用
get
返回的指针来初始化一个shared_ptr
对象,这种的做法的本质与第2点一样,会造成未定义的行为。 - 尽量不要保存
get
函数返回的指针,因为你不知道什么时候这个指针对应的对象就被析构掉了,所以请“随用随取”。
示例传送门:shared_ptr用法