C++11智能指针shared_ptr


本文主要讲解C++内容分配的智能指针shared_ptr、make_shared。

在C++中一般的用法是new+delete管理堆内存。注意,局部变量是放在了栈区,栈区的变量自动分配和释放。而new和delete产生的变量(内存)放在了堆区,需要手动分配和释放。在c语言中分配空间的方法是malloc和free,在此不再涉及了。而c++11中智能指针只需手动分配,无需手动释放,程序会自动释放堆内存。

在讲解智能指针前想先回顾下new+delete的用法。

1.new+delete

C++中,动态内存的管理是通过一对运算符来完成的:new+delete。

  • new在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对其进行初始化。
  • delete 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

1.1内存的分配

int *pi=new int;//new创建了一个动态的,未初始化的无名对象,返回指向该对象的指针
int *pi=new int(); //,在类型名后跟一对圆括号即代表初始化了。这例初始化0了
string *ps=new string; //初始化为空的string,因为相当于类类型对象将用默认构造函数进行初始化
string *ps=new string(); 

// 支持构造函数用法
string *ps=new string(5,'a');
int *pi=new int(5);
vector<int> *pv=new vector<int>{0,2,3};

// 可以使用auto让初始化器推断对象类型。此方法由于编译器要用初始化器的类型来推断要分配的类型,只有当括号中仅有一初始化器时才可以使用auto
auto p1= new auto(obj);
// auto p2 = new auto{a,b,c}//错误,括号中只能有的那个初始化器

// 动态分配的const对象
const int *pci=new const int(1024);
const string *pcs = new const string;// 类似于其他const对象,动态分配的const对象必须进行初始化。这里的string是隐式初始化了

1.2delete释放堆内存

//普通的指针变量
int * p=new int;
delete p;
delete p;
// 数组指针要在中间加个[]
int *pa = new int[3];
delete [] pa;
// 结构体等符合类型要先释放其中指针成员的内存再释放结构体指针

2智能指针之共享指针:shared_ptr

因为堆内存需要手动释放,程序员经常忘记释放,所以C++11新加入了智能指针。c++11中智能指针只需手动分配,无需手动释放,程序会自动释放堆内存。

首先想先提一下,之前听过一个面试题是:你怎么实现智能指针?答案是:可以利用类的构造函数是析构函数。尽管其实智能指针不只是这么简单,但我们理解到析构函数这里也就可以了。(析构函数如局部变量一样,在离开作用域{}后,会自动触发析构函数。)在这里添加一句就更能把智能指针理解透了:如果返回值是局部作用域中创建的智能指针,那么智能指针作为函数返回值的时候,智能指针暂且不在当前作用域中触发析构函数释放内存,会传到调用者中释放内存。(尽管事实上不合理,但没必要了解更多了。)

如何使用智能指针?

头文件include<memory>

2.1创建一个智能指针:shared_ptr、make_shared

智能指针是个模板,需要传入指针所指向内存的类型。T为类型,如int,string,struct,class等

shared_ptr<T> sp;//只是创建了一个智能指针,未分配内存
shared_ptr<T> ptr(new T);//创建了一个智能指针且分配了内存
shared_ptr<T> ptr2(ptr);//ptr和ptr2指向同一块内存,智能指针内部有内存引用数计算器
auto ptr2(ptr);// 编译器能推导出新指针类型时,可用auto代替

// 应该更好地使用make_shared,理由稍后再讲
shared_ptr<string> p1 = make_shared<string>;//创建智能指针,且分配了内存,这种用法稍后再讲
auto p1 = make_shared<string>();  // 联想类,有误括号()即可
shared_ptr<string> p1 = make_shared<string>(10, '9');//string内容为10个9 
shared_ptr<string> p2 = make_shared<string>("hello"); 

// 除了复合类型,无需考虑释放的问题。因为智能指针不能很好地释放复合类型的指针(内部还有成员指针),所以需要传入自己写的析构函数。此时第一个参数为分配内存函数,第二个为重载的析构函数。
shared_ptr<T> sp(new struct student,析构函数);




因为

2.2检查创建的智能指针是否成功创建

if(!ptr1)// 方法1
	std::cout<<"ptr1 is empty"<<std::endl;
if(ptr1 == nullptr)// 方法2
	std::cout<<"ptr1 is empty"<<std::endl;
if(ptr1 == NULL)// 方法2
	std::cout<<"ptr1 is empty"<<std::endl;

2.3构造函数防错注意事项

  • 不要使用同一个原始指针构造 shared_ptr
创建多个 shared_ptr 的正常方法是使用一个已存在的shared_ptr 进行创建,而不是使用同一个原始指针进行创建。
int *num = new int(23);
std::shared_ptr<int> p1(num);
std::shared_ptr<int> p2(p1);  // 正确使用方法
std::shared_ptr<int> p3(num); // 不推荐
std::cout << "p1 Reference = " << p1.use_count() << std::endl; // 输出 2
std::cout << "p2 Reference = " << p2.use_count() << std::endl; // 输出 2
std::cout << "p3 Reference = " << p3.use_count() << std::endl; // 输出 1
假如使用原始指针num创建了p1,又同样方法创建了p3,当p1超出作用域时会调用delete释放num内存,此时num成了悬空指针,当p3超出作用域再次delete的时候就可能会出错。
  • 不要用栈中的指针构造 shared_ptr 对象
#include<iostream>
#include<memory>
int main()
{
   int x = 12;
   std::shared_ptr<int> ptr(&x);//当 shared_ptr 对象超出作用域调用析构函数delete 指针&x时会出错。
   return 0;
}

  • 建议使用make_shared
为了避免以上两种情形,建议使用make_shared()<>创建 shared_ptr 对象,而不是使用默认构造函数创建。
用法:
std::shared_ptr<int> ptr_1 = make_shared<int>();
std::shared_ptr<int> ptr_1 = make_shared<int>(11);
std::shared_ptr<int> ptr_2 (ptr_1);

另外不建议使用get()函数获取 shared_ptr 关联的原始指针,因为如果在 shared_ptr 析构之前手动调用了delete函数,同样会导致类似的错误。
  • 其它初始化智能指针的方法,如下;不推荐!
/*不推荐*/
int * p = new int(32);
shared_ptr<int> pp(p);
cout<<*pp<<endl;

    /*意外的情况*/
//    delete p;               //!不小心把delete掉了。
//    cout<<*pp<<endl;·       //!pp也不再有效。

2.4智能指针的释放

多个 shared_ptr 对象可以共同托管一个指针 p,当所有曾经托管 p 的 shared_ptr 对象都解除了对其的托管时,就会执行`delete p`。

但如果我们创建了一个指向结构体的智能指针,而结构体内有指针成员,则释放结构体时需要先释放其中的指针成员内容,这使默认的析构函数就不符合要求了,我们需要手写析构函数传入其中。这个析构函数可以正常写,也可以写成lambda表达式。析构函数的传入方法是跟在new参数后面,处于第2个形参位置。

```C
class Deleter
{
	public:
	void operator() (Sample * x) {
		std::cout<<"DELETER FUNCTION CALLED\n";
		delete[] x;
	}
};
// 函数对象作为删除器
std::shared_ptr<Sample> p3(new Sample[3], Deleter());

// Lambda表达式作为删除器
std::shared_ptr<Sample> p4(new Sample[3],
                           [](Sample * x){delete[] x;}
                          );

再比如对于动态数组:
class DelTest;
shared_ptr<DelTest> p(new DelTest[10],[](DelTest *p){delete[] p;});
通过自定义删除器的方式shared_ptr虽然管理的是一个动态数组。但是shard_ptr并【不支持下标运算符】的操作。而且智能指针类型【不支持指针】算术运算(不能取地址)。因此为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素。

2.5运算符重载

与普通指针相比,shared_ptr仅提供->*==运算符,没有+-++--[]等运算符。

p // 将p用作一个条件判断,若p指向一个对象,则为true
*p // 解引用p获得它指向的对象
p->mem // 等价于(*p).mem
p.get() //返回p中保存的指针。要小心使用,若只能指针释放了其对象返回的指针所指向的对象也就消失了。所以尽量不要把裸指针取出来

2.6智能指针成员函数方法之:get()

智能指针定义了一个名为get的函数,它返回一个内置指针,【指向智能指针的管理的对象】。此函数设置的初衷是当我们向不能使用智能指针的代码传递一个内置指针。【使用get返回指针的代码不能delete此指针】。

#include <iostream>
#include <memory>
using namespace std;
void useShared_ptr(int *p){   cout<<*p<<endl;}
void delePointer(int *p){delete p;}

int main(int argc, char *argv[])
{
    shared_ptr<int> p1 = make_shared<int>(32);
//    shared_ptr<int>p2(p1.get());  //!错误的用法:但是p1、p2各自保留了对一段内存的引用计数,其中有一个引用计数耗尽,资源也就释放了。
    useShared_ptr(p1.get());
//    delePointer(p1.get());        //!error:
    return 0;
}

再次声明:get用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get。特别是,【永远不要用get初始化另一个智能指针或者为另一个智能指针赋值】!

2.7智能指针成员函数方法之:use_count()

无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当【用一个shared_ptr去初始化另一个shared_ptr】;当我们给shared_ptr赋予一个新的值或者是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,他就会自动释放自己所管理的对象。

当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象:

ptr.use_count()返回引用个数

#include <iostream>
using namespace std;
int main()
{
    auto p = make_shared<int>(42); //!p指向的对象只有p一个引用者。
    cout<<p.use_count()<<endl;
    auto q(p);                     //!p和q指向相同的对象,此对象有两个引用者。
    cout<<p.use_count()<<endl;
    return 0;
}

2.8智能指针成员函数方法之:reset()

reset:我们可以用reset将一个新的指针赋予一个shared_ptr:

p.reset();//释放p这个智能指针,该指向的内容计数减一
p.reset(qq);//原有p的指向的计数减一,重新指向一个新内存,与qq指向相同
p.reset(qq,delete析构函数);

它将在内部指向新指针,因此其引用计数将再次变为1。

与赋值类似,reset会更新(-1)引用计数,如果需要的话,会释放p1指向的对象。reset成员经常与unique一起使用,来控制多个shared_ptr的共享对象。在改变底层对象之前,我们在检查自己是否是当前对象仅有的用户。如果不是,在改变之前要做一份新的拷贝:

#include <iostream>
#include <memory>
using namespace std;
int main()
{
    shared_ptr<string> p1(new string("helloworld--1"));
//    p1 = new string("helloworld2--2");//error!
    p1.reset(new string("helloworld2--2"));
    cout<<*p1<<endl;
}

2.9智能指针成员函数方法之:swap()

swap(p,q) // 交换p、q中的指针
p.swap(q) // 交换p、q中的指针

2.10shared_ptr作返回值:

#include <iostream>
#include<string>
using namespace std;

shared_ptr<string> factory(const char* p){
    return make_shared<string>(p);
}

void use_factory(){
    shared_ptr<string> p = factory("helloworld"); 
    cout<<*p<<endl;   //!离开作用域时,p引用的对象被销毁。
} 
shared_ptr<string> return_share_ptr(){
    shared_ptr<string> p = factory("helloworld"); 
    cout<<*p<<endl; 
    return p; //!返回p时,引用计数进行了递增操作。 
}  //  p离开了作用域,但他指向的内存不会被释放掉。 
int main() {   
    use_factory();
    auto p = return_share_ptr(); 
    cout<<p.use_count()<<endl;
}

附:管理非常规动态对象

某些情况下,有些动态内存也不是我们new出来的,如果要用shared_ptr管理这种动态内存,也要自定义删除器。

#include <iostream>
#include <stdio.h>
#include <memory>
using namespace std;

void closePf(FILE * pf)
{
    cout<<"----close pf after works!----"<<endl;
    fclose(pf);
}

int main()
{
    shared_ptr<FILE> pf(fopen("bin2.txt", "w"),closePf);
    if(!pf) return -1;
    char *buf = "abcdefg";
    fwrite(buf, 8, 1, pf.get());    //!确保fwrite不会删除指针的情况下,可以将shared_ptr内置指针取出来。//无需fclose
}  

3智能指针之独占指针:unique

大体与shared_ptr用法相同。

区别:

unique_ptr 独享所有权
unique_ptr对象始终是关联的原始指针的唯一所有者。我们无法复制unique_ptr对象,它只能移动。
由于每个unique_ptr对象都是原始指针的唯一所有者,因此在其析构函数中它直接删除关联的指针,不需要任何参考计数。

构造
std::unique_ptr<int> ptr1;
std::unique_ptr<Task> taskPtr(new Task(22));
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);

不能通过赋值的方法创建对象,下面的这句是错误的
// std::unique_ptr<Task> taskPtr2 = new Task(); // 编译错误

简单提一下unique也有的方法

reset()、

unique_ptr 对象不可复制
由于 unique_ptr 不可复制,只能移动。因此,我们无法通过复制构造函数或赋值运算符创建unique_ptr对象的副本。

// 编译错误 : unique_ptr 不能复制
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error

// 编译错误 : unique_ptr 不能复制
taskPtr = taskPtr2; //compile error

转移 unique_ptr 对象的所有权
我们无法复制 unique_ptr 对象,但我们可以转移它们。这意味着 unique_ptr 对象可以将关联的原始指针的所有权转移到另一个 unique_ptr 对象。让我们通过一个例子来理解:

// 通过原始指针创建 taskPtr2
std::unique_ptr<Task> taskPtr2(new Task(55));
// 把taskPtr2中关联指针的所有权转移给taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 现在taskPtr2关联的指针为空
if(taskPtr2 == nullptr)
	std::cout<<"taskPtr2 is  empty"<<std::endl;

// taskPtr2关联指针的所有权现在转移到了taskPtr4中
if(taskPtr4 != nullptr)
	std::cout<<"taskPtr4 is not empty"<<std::endl;

// 会输出55
std::cout<< taskPtr4->mId << std::endl;

std::move() 将把 taskPtr2 转换为一个右值引用。因此,调用 unique_ptr 的移动构造函数,并将关联的原始指针传输到 taskPtr4。在转移完原始指针的所有权后, taskPtr2将变为空。

释放关联的原始指针
在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。

std::unique_ptr<Task> taskPtr5(new Task(55));
// 不为空
if(taskPtr5 != nullptr)
	std::cout<<"taskPtr5 is not empty"<<std::endl;
// 释放关联指针的所有权
Task * ptr = taskPtr5.release();
// 现在为空
if(taskPtr5 == nullptr)
	std::cout<<"taskPtr5 is empty"<<std::endl;

weak_ptr

weakptr使用的比较少,如有兴趣了解,请去参考该篇文章:https://www.cnblogs.com/DswCnblog/p/5628314.html

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shared_ptrC++中的智能指针,它采用引用计数的方法来实现释放指针所指向的资源。当使用shared_ptr时,它会记录有多少个shared_ptr指向同一个对象,只有当最后一个shared_ptr被销毁时,该对象的内存才会被释放。因此,shared_ptr可以自动管理内存,不需要手动释放。 在代码中,使用shared_ptr可以像普通指针一样操作对象。当需要创建一个shared_ptr对象时,可以使用std::make_shared函数来构造,如下所示: ``` std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(10); ``` 可以通过将shared_ptr赋值给其他shared_ptr来共享资源,如下所示: ``` std::shared_ptr<int> sharedPtr2 = sharedPtr1; ``` 当所有的shared_ptr都被销毁时,内存会自动释放。可以使用use_count()函数获取shared_ptr的引用计数,如下所示: ``` std::cout << "sharedPtr2 use count: " << sharedPtr2.use_count() << std::endl; ``` 当引用计数为0时,内存会被自动释放。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++智能指针shared_ptr分析](https://download.csdn.net/download/weixin_38705004/13788082)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C++11中的智能指针unique_ptrshared_ptr和weak_ptr详解](https://blog.csdn.net/chenlycly/article/details/130918547)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值