C++智能指针详解

从比较简单的层面来看,智能指针是RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制对普通指针进行的一层封装。这样使得智能指针的行为动作像一个指针,本质上却是一个对象,这样可以方便管理一个对象的生命周期

优缺点:

  • 智能指针由原始指针的封装,优点是可以自动分配内存,不用担心内存泄漏问题。
  • 用于解决独占/共享所有权指针的释放,传输等问题。
  • 但是没有原始指针方便。

一. unique_ptr独占指针

特点

都是围绕独占展开

特点一: 如其名,独占。也就是说同一个内存空间同时只能有一个指针来管理。

int* pi = new int(10); //利用传统指针在堆区开辟一个存放整数的区域 std::unique_ptr u_pi_01{pi};//通过传统指针创建智能指针 std::unique_ptr u_pi_02{pi};//有意让两个独占指针同时指向同一个内存区域

这么写编译器不会报错,但运行时会提示 error:double free detected in tcache 2

这也就印证了第一个特点,一个内存区域只能由一个指针管理。

特点二: 当指针超出作用域时,内存自动释放

//由于指针的本质也是变量,离开作用范围就会自动释放 //因此我们需要通过在外部创建变量来保存指针所保存的地址 int* external_pi_1;//用于存储传统指针的地址 int* external_pi_2;//用于存储智能指针的地址 { int *pi = new int(10);//使用传统指针在堆区开辟内存存储整形 external_pi_1 = pi; std::unique_ptr u_pi{new int(10)};//使用智能指针在堆区开辟内存存储 external_pi_2 = u_pi.get(); std::cout << *external_pi_1 << std::endl;//输出10 std::cout << *external_pi_2 << std::endl;//输出10 } std::cout << *external_pi_1 << std::endl;//输出10 std::cout << *external_pi_2 << std::endl;//输出0

可见传统指针在局部作用域中开辟的内存在外部同样可以访问,也就是说我们使用传统指针开辟内存之后在离开作用域时需要加上释放内存的操作,不然会造成内存泄漏。

而智能指针我们不需要手动释放内存,在离开作用域后会自动释放。

特点三:由于特点一,修改指针不可以copy ,只能Move(转移归属权)

std::unique_ptr u_pi1 = std::make_unique(10); //std::unique_ptr u_pi2 = u_pi1;//尝试用拷贝的方式共享内存,error:可别忘了这是独占指针 std::unique_ptr u_pi2 = move(u_pi1);使用move方法转移内存拥有权。

也就是说,通过move函数,把指针u_pi1所指内存中的值掏空,然后安到指针u_pi2所指的内存上。

创建方式

方式一: 通过已有的传统指针创建

int* pi = new int(10); //使用传统指针在堆区开辟一个空间 std::unique_ptr u_pi{pi};//利用创通指针创建智能指针

方式二: 通过new方法创建

std::unique_ptr u_pi{new int(10)};

方式三: 通过std :: make_unique创建

std::unipue_ptr u_pi = std::make_unique(10);

传递方式

方式一: 通过move(),转移拥有权.

void show(std::unique_ptr u_pi) { std::cout<<*u_pi< u_pi{new int(10)}; show(move(u_pi)); //通过move转移拥有权 }

注意:将指针的拥有权转入函数中后,在原作用域指针将被释放,而该指针将在函数调用结束时释放。也就是说,将智能指针以move的形式传入函数后,在原作用域不能再使用该指针。

方式二: 通过引用传递

void show(const std::unique_ptr &u_pi)//加cosnt 不是不能改变指向的值,不能改变指针的指向 { std::cout << *u_pi << std::endl; //u_pi.reset();加了const所以不能清空 } void test() { std::unique_ptr u_pi{new int(10)}; show(u_pi); }

注意: 将指针以引用的方式传入函数,那么该指针在原作用域依然存活,并可以和所调用函数共同操作该内存空间数据。

方式三: 链式传递

std::unique_ptr get_unique(std::string str) { std::unique_ptr u_pi{new Person(str)}; return u_pi; } void test() { get_unique("hua")->show();//链式 }

简单使用

  • 通过get()获取地址
  • 可以通过->调用成员函数
  • 可以通过*调用解引用
  • 通过reset()清空指针
class Person { public: Perosn(std::string name):m_name(name){}; void show() { std::cout<<"name is "< u_p{new Person("kimi")}; //用自定义类型创建 u_p->show();//可以通过->调用函数 (*u_p).show();//通过*解引用 std::cout<

隐藏危险

用已有指针创建时,没有及时清空传统指针,导致同时有两个指针指向这块已经被“独占”的区域。

int* pi = new int(10); std::unique_ptr u_pi{pi};//使用传统指针创建,上式开辟的区域被独占 *pi = 20; //没有及时清空,依然可以通过独占指针以外的方式修改内存

二. shared_ptr 计数指针

特点

特点一: 可以通过copy共享内存。

std::shared_ptr u_pi_1{new int(10)}; std::shared_ptr u_pi_2 = u_pi_1;//通过复制拷贝

特点二: 通过use_count();来查看计数 ,copy 计数加一,销毁计数减一。

std::shanred_ptr s_pi{new int(10)}; //s_pi.use_count() == 1 std::shanred_ptr s_pi_copy = s_pi;  //s_pi.use_count() == 2 s_pi = nullptr;//清空指针                 //s_pi_copy.use_count() == 1

特点三: 无论多少指针,都同用一份数据,因而同一份数据的use_count()一致。

std::shared_ptr s_pi{new int(10)}; //s_pi.use_count() == 1 std::shared_ptr s_pi_2 = s_pi;     //s_pi.use_count() == 2 std::shared_ptr s_pi_3 = s_pi_2;   //s_pi.use_count() == 3 s_pi_2 = bullptr; //清空2指针            //s_pi.use_count() == 2

传递方式

  • 本质不变,在函数调用中,因为本身支持复制操作,所以不用加move可以直接传递。
  • 并且在传递到函数中,use_count() 会增加,并在函数销毁时候还原。
  • 在函数中修改指向的值,在外部的指针指向的值也会改变。
  • 使用引用传递,则在传递到函数中时,计数不会增加。
void get_use_1(std::shared_ptr s_pi) { std::cout << s_pi.use_count() << std::endl; } void get_use_2(std::shared_ptr& s_pi) { std::cout << s_pi.use_count() << std::endl; } void test() { std::shared_ptr s_pi{new int(10)}; std::cout << s_pi.use_count() << std::endl; get_use_1(s_pi);//在函数中计数会增加,但随着函数销毁,计数复原 get_use_2(s_pi);//以引用方式传入,指针还是那个指针,计数不会增加 }

输出:1 2 1

隐藏危险

share_ptr带来的循环依赖问题

class Person { public: void set_friend(share_ptr p) _friend = p; private: share_ptr _frient; }; int main() { share_ptr p1 = make_shared("P1"); share_ptr p2 = make_shared("P2"); p1->set_friend(P2); p2->set_friend(P1);//造成循环依赖,在main中的话,不会执行析构 }

解决:将_friend属性改为weak_ptr 。

三. weak_ptr

weak_ptr 是一个不需要所有权的指针,所以我们可以通过用weak_ptr来声明属性,解决循环依赖

class Person { public: void set_friend(share_ptr p) _friend = p; private: weak_ptr _frient;//使用weak_ptr解决循环依赖 }; int main() { share_ptr p1 = make_shared("P1"); share_ptr p2 = make_shared("P2"); p1->set_friend(P2); p2->set_friend(P1); }

可以通过lock()来将weak_pte升级为shared_ptr;

std::weak_ptr w_pi{new Person("hua")}; std::shared_ptr  s_pi2 = w_pi.lock();

到此这篇关于C++智能指针详解的文章就介绍到这了,更多相关C++智能指针内容请搜索0133技术站以前的文章或继续浏览下面的相关文章希望大家以后多多支持0133技术站!

以上就是C++智能指针详解的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » C语言