智能指针与内存管理 1.RAII与引用计数 引用计数这种计数是为了防止内存泄露而产生的。 基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次, 每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
所以通常的做法是对于一个对象而言,我们在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间, 也就是我们常说的 RAII 资源获取即初始化技术。
在传统 C++ 里我们只好使用 new 和 delete 去 『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。 这些智能指针包括 std::shared_ptr/std::unique_ptr/std::weak_ptr,使用它们需要包含头文件 。
std::shared_ptr std::shared_ptr 是一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象,从而消除显式的调用 delete,当引用计数变为零的时候就会将对象自动删除。
但还不够,因为使用 std::shared_ptr 仍然需要使用 new 来调用,这使得代码出现了某种程度上的不对称。
std::make_shared 就能够用来消除显式的使用 new,所以std::make_shared 会分配创建传入参数中的对象, 并返回这个对象类型的std::shared_ptr指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <memory> void foo (std::shared_ptr<int > i) { (*i)++; } int main () { auto pointer = std::make_shared <int >(10 ); foo (pointer); std::cout << *pointer << std::endl; return 0 ; }
std::shared_ptr 可以通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数, 并通过use_count()来查看一个对象的引用计数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 auto pointer = std::make_shared <int >(10 );auto pointer2 = pointer; auto pointer3 = pointer; int *p = pointer.get (); std::cout << "pointer.use_count() = " << pointer.use_count () << std::endl; std::cout << "pointer2.use_count() = " << pointer2.use_count () << std::endl; std::cout << "pointer3.use_count() = " << pointer3.use_count () << std::endl; pointer2.reset (); std::cout << "reset pointer2:" << std::endl; std::cout << "pointer.use_count() = " << pointer.use_count () << std::endl; std::cout << "pointer2.use_count() = " << pointer2.use_count () << std::endl; std::cout << "pointer3.use_count() = " << pointer3.use_count () << std::endl; pointer3.reset (); std::cout << "reset pointer3:" << std::endl; std::cout << "pointer.use_count() = " << pointer.use_count () << std::endl; std::cout << "pointer2.use_count() = " << pointer2.use_count () << std::endl; std::cout << "pointer3.use_count() = " << pointer3.use_count () << std::endl;
std::unique_ptr std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:
1 2 std::unique_ptr<int > pointer = std::make_unique <int >(10 ); std::unique_ptr<int > pointer2 = pointer;
既然是独占,换句话说就是不可复制。但是,我们可以利用 std::move 将其转移给其他的 unique_ptr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <memory> struct Foo { Foo () { std::cout << "Foo::Foo" << std::endl; } ~Foo () { std::cout << "Foo::~Foo" << std::endl; } void foo () { std::cout << "Foo::foo" << std::endl; } }; void f (const Foo &) { std::cout << "f(const Foo&)" << std::endl; } int main () { std::unique_ptr<Foo> p1 (std::make_unique<Foo>()) ; if (p1) p1->foo (); { std::unique_ptr<Foo> p2 (std::move(p1)) ; f (*p2); if (p2) p2->foo (); if (p1) p1->foo (); p1 = std::move (p2); if (p2) p2->foo (); std::cout << "p2 被销毁" << std::endl; } if (p1) p1->foo (); }
std::weak_ptr std::weak_ptr 是一种智能指针,通常不单独使用,只能和 shared_ptr 类型指针搭配使用,可以视为 shared_ptr 指针的一种辅助工具。借助 weak_ptr 类型指针可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、通过expired()判断shared_ptr 指针指向的堆内存是否已经被释放等等,还可以解决shared_ptr 循环引用的问题。
weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快。表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <memory> int main () { { std::shared_ptr<int > sh_ptr = std::make_shared <int >(10 ); std::cout << sh_ptr.use_count () << std::endl; std::weak_ptr<int > wp (sh_ptr) ; std::cout << wp.use_count () << std::endl; if (!wp.expired ()){ std::shared_ptr<int > sh_ptr2 = wp.lock (); *sh_ptr = 100 ; std::cout << wp.use_count () << std::endl; } } std::weak_ptr<int > wp; { std::shared_ptr<int > sh_ptr = std::make_shared <int >(10 ); wp = sh_ptr; std::cout << std::boolalpha << wp.expired () << std::endl; } std::cout << std::boolalpha << wp.expired () << std::endl; return 0 ; }
weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <memory> int main (int argc, const char * argv[]) { std::shared_ptr<int > sp (new int (10 )) ; std::weak_ptr<int > wp (sp) ; if (std::shared_ptr<int > pa = wp.lock ()) { std::cout << *pa << std::endl; } else { std::cout << "wp指向对象为空" << std::endl; } sp.reset (); if (std::shared_ptr<int > pa = wp.lock ()) { std::cout << *pa << std::endl; } else { std::cout << "wp指向对象为空" << std::endl; } return 0 ; }
weak_ptr的一个作用是解决share_ptr的循环引用问题。如下面代码所示,class AA中含有指向class BB的shared指针, class BB 中含有指向class AA的shared指针,这样形成了循环引用。m_bb_ptr和m_aa_ptr的强引用计数永远大于等于1,所以直到程序退出前都不会被退出,这种情况有时候在正常的业务逻辑中是不可避免的,而解决循环引用的方法是改用weak_ptr:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class BB ; class AA { public : AA () { cout << "AA::AA() called" << endl; } ~AA () { cout << "AA::~AA() called" << endl; } shared_ptr<BB> m_bb_ptr; }; class BB { public : BB () { cout << "BB::BB() called" << endl; } ~BB () { cout << "BB::~BB() called" << endl; } shared_ptr<AA> m_aa_ptr; }; int main () { shared_ptr<AA> ptr_a (new AA) ; shared_ptr<BB> ptr_b (new BB) ; cout << "ptr_a use_count: " << ptr_a.use_count () << endl; cout << "ptr_b use_count: " << ptr_b.use_count () << endl; ptr_a->m_bb_ptr = ptr_b; ptr_b->m_aa_ptr = ptr_a; cout << "ptr_a use_count: " << ptr_a.use_count () << endl; cout << "ptr_b use_count: " << ptr_b.use_count () << endl; return 0 ; }
可以看到由于AA和BB内部的shared_ptr各自保存了对方的一次引用,所以导致了ptr_a和ptr_b销毁的时候都认为内部保存的指针计数没有变成0,所以AA和BB的析构函数不会被调用。解决方法就是把一个shared_ptr替换成weak_ptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class BB ;class AA { public : AA () { cout << "AA::AA() called" << endl; } ~AA () { cout << "AA::~AA() called" << endl; } weak_ptr<BB> m_bb_ptr; }; class BB { public : BB () { cout << "BB::BB() called" << endl; } ~BB () { cout << "BB::~BB() called" << endl; } shared_ptr<AA> m_aa_ptr; };
总结: 1.weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。 2.weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。 3.weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。 4.由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。
原文链接: https://kettycode.github.io/2024/02/06/cpp/现代C++/base6/
版权声明: 转载请注明出处.