C/C++编程:智能指针 ╰半夏微凉° 2022-12-19 09:18 211阅读 0赞 > 计算机系统中的资源有很多种,内存是我们最常用到的,此外还有文件描述符,socket、操作系统handler,数据库连接等,程序里申请这些资源之后必须及时归坏系统,否则就会产生难以预料的后果 std::unique_ptr<char[]> buffer(new char[1024]); ### 文章目录 ### * 疑惑 * * 引用与非引用做返回值(shared\_ptr) * 引用&非引用做参数(shared\_ptr) * RAII机制 * 显式内存管理 * 智能指针 * * scoped\_ptr * * 类摘要 * 操作函数 * 实践 * 源码阅读 * unique\_ptr * shared\_ptr * * 实践 * 初始化 * 关于get()函数 * 关于make\_shared函数 * shared\_ptr的拷贝和赋值 * shared\_ptr作返回值 * 容器中的shared\_ptr * shared\_ptr对象的销毁 * 状态共享 * 智能指针与异常 * 源码 * weak\_ptr * * 用法 # 疑惑 # ## 引用与非引用做返回值(shared\_ptr) ## #include <fstream> // ifstream, ifstream::in #include <iostream> #include <fcntl.h> #include <zconf.h> #include <memory> #include <map> using namespace std; struct Foo{ Foo(){ printf("%s", "Foo:Foo\n");} ~Foo(){ printf("%s", "~Foo:Foo\n");} void foo(){ printf("%s", "====Foo:foo\n");} }; std::map<std::string , std::shared_ptr<struct Foo>> m_dev_info; void init(){ std::shared_ptr<struct Foo> dev_info = make_shared<struct Foo>(); m_dev_info["aaa"] = dev_info; } shared_ptr<struct Foo> & get_dev_yinyong(const std::string& camera_id){ //引用计数不加1 return m_dev_info["aaa"]; } shared_ptr<struct Foo> get_dev_noyinyong(const std::string& camera_id){ // 引用计数加1 return m_dev_info["aaa"]; } int main(){ init(); printf("%ld\n", m_dev_info["aaa"].use_count()); shared_ptr<struct Foo> dev_info; // shared_ptr<struct Foo> & i = get_dev_yinyong("aaa"); // printf("%ld\n", m_dev_info["aaa"].use_count()); shared_ptr<struct Foo> f = get_dev_noyinyong("aaa"); printf("%ld\n", m_dev_info["aaa"].use_count()); getchar(); return 0; } ## 引用&非引用做参数(shared\_ptr) ## #include <fstream> // ifstream, ifstream::in #include <iostream> #include <fcntl.h> #include <zconf.h> #include <memory> #include <map> using namespace std; struct Foo{ Foo(){ printf("%s", "Foo:Foo\n");} ~Foo(){ printf("%s", "~Foo:Foo\n");} void foo(){ printf("%s", "====Foo:foo\n");} }; std::map<std::string , std::shared_ptr<struct Foo>> m_dev_info; void init(){ std::shared_ptr<struct Foo> dev_info = make_shared<struct Foo>(); m_dev_info["aaa"] = dev_info; } bool get_dev_info(const std::string& camera_id, shared_ptr<struct Foo> &infoTag){ //引用计数+1 infoTag = m_dev_info["aaa"]; return true; } bool get_dev_info1(const std::string& camera_id, shared_ptr<struct Foo> infoTag){ // 引用计数不加1 infoTag = m_dev_info["aaa"]; return true; } int main(){ init(); printf("%ld\n", m_dev_info["aaa"].use_count()); shared_ptr<struct Foo> dev_info; get_dev_info("aaa", dev_info); printf("%ld\n", m_dev_info["aaa"].use_count()); getchar(); return 0; } # RAII机制 # 为了管理内存等资源,C++程序员中通常采用RAII机制(资源获取即初始化),在**类的构造函数中申请资源**,然后使用,最后**在析构函数中释放资源**。 * 如果对象使用声明在栈上构建的,那么当对象离开作用域时对象会自动销毁从而调用析构函数释放资源 * 如果对象是用new在堆上创建的,那么必须由程序员手动的调用delete操作符销毁它才能释放资源。如果忘记释放,就会存在资源泄漏的隐患;还有种可能是因为某些意外导致程序未能执行delete语句,也会造成内存丢失。 ![在这里插入图片描述][20201106162110563.png_pic_center] # 显式内存管理 # 实际编写C/C++程序的时候,常会遇到比如程序允许是忽然退出,或者占用的内存越来越多,最后不得不定期重启的一些现象。这些问题的源头可以追溯到C/C++中的显式堆内存管理上(原因一般是没有正确的处理堆内存的分配和是否)。问题一般由三个: * 野指针:一些内存单元已经被释放,之前指向它的指针却还在被使用。这些内存有可能被运行时系统重新分配给了程序使用,从而导致无法预测的错误。 * 重复释放:程序试图去释放已经释放过的内存单元,或者释放已经被重新分配过的内存单元,就会导致重复释放错误。通常重复释放内存会导致C/C++运行时系统打印出大量错误和诊断信息 * 内存泄漏:不需要再使用的内存单元如果没有被释放就会导致内存泄漏。如果程序不断地重复进行这类操作,将会导致内存占用剧增。 虽然显式的管理内存在性能上有一些优势,但是也非常容器出错。因此一个理念就是编程语言应该提供更好的机制,让程序员摆脱内存管理的细节。在C++中,一个这样的机制就是标准库中的智能指针。进一步的,标准库还提供了所谓”最小垃圾回收“支持。 # 智能指针 # > 智能指针可以让对象在退出作用域时(不管是正常离开而是异常离开),调用delete来析构在堆上动态分配的对象 boost.smart\_ptr库提供了六种智能指针:`scoped_ptr`,`shared_ptr`、`weak_ptr`、`instrusive_ptr`,`scoped_array`、`shared_array`、 * 它们都是轻量级的对象,速度和原始指针相差几无,都是异常安全(`exception safe`)的 * 而且对于所指向的类型T也仅有一个很小很合理的要求:类型T的析构函数不能抛出异常 * `scoped_array`、`shared_array`很少用。 C++标准库提供的智能指针:`weak_ptr`、`shared_ptr`、`unique_ptr`。它们是基于boost的smart\_ptr库的 boost库的只能指针都位于名字空间`boost`,需要包含头文件`#include <boost/smart_ptr.hpp>`,即使: #include <boost/smart_ptr.hpp> using namespace boost; 补充: * `auto_ptr`:最早的智能指针, 是C++98标准化引入的 * auto\_ptr虽说简单,但使用起来却到处是坑,以至于大家都不提倡使用(**无论在什么情况下都不要使用**) * `std::auto_ptr`已在标准库中声明为飞起,现在应该使用新的智能指针`std::unique_ptr` ## scoped\_ptr ## * 这个智能指针只能在本作用域里面使用,不能被转让 * 它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确的删除。 ### 类摘要 ### ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70]![在这里插入图片描述][20210207173045543.png] ### 操作函数 ### * `scoped_ptr`的构造函数 * 接受一个类型为T\*的指针p,创建出一个scoped\_ptr对象,并在内部参数保存指针参数p。p必须满足下面两种要求: * p要么是一个new表达式动态分配的结果 * p要么是一个空指针nullptr * 当scoped\_ptr对象的生命期结束时,析构函数对使用delete操作符自动销毁所保存的对象,从而正确的回收资源 * `scoped_ptr`把拷贝构造函数和赋值函数都声明为私有的,禁止拷贝或者赋值,从而**保证了被它管理的指针不能转让所有权,只能在`scoped_ptr`声明的作用域内使用** * `reset`: 重置scoped\_ptr, 它删除了原来保存的指针,再保存新的指针值p。 * 如果p是空指针,那么scoped\_ptr将不再持有任何指针 * 一般情况下,reset不应该被调用,因为它违背了scoped\_ptr的本意–》资源应该一直由scoped+pty自己自动管理。 ### 实践 ### 例子: #include <boost/smart_ptr.hpp> using namespace std; using namespace boost; class X { public: X(){ cout << "X ..." << endl; } ~X(){ cout << "~X ..." << endl; } }; int main(int argc,char *argv[]){ cout << "Entering main ..." << endl; { boost::scoped_ptr<X> pp( new X); //boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移 } cout << "Exiting main ..." << endl; return 0; } ![在这里插入图片描述][2020110616561647.png_pic_center] 例子 #include <boost/smart_ptr.hpp> using namespace std; using namespace boost; int main(int argc,char *argv[]){ scoped_ptr<string> scopedPtr(new string("text")); // scoped_ptr是一个对象而不是指针,没有办法delete的。 assert(scopedPtr != nullptr); cout << *scopedPtr << endl; // text : * 取出字符串的内容 cout << scopedPtr->size() << endl; //4 : -> 字符串的长度 scopedPtr.reset(); assert(scopedPtr == nullptr); return 0; } ### 源码阅读 ### template<class T> class scoped_ptr // noncopyable { private: T * px; // 原始指针 // 不能拷贝/赋值:保证了被它管理的指针不能被转让所有权 scoped_ptr(scoped_ptr const &); // 拷贝构造函数私有化--->不能拷贝 scoped_ptr & operator=(scoped_ptr const &); // 赋值函数私有化 ---> 不能赋值 typedef scoped_ptr<T> this_type; void operator==( scoped_ptr const& ) const; // 相等函数私有化 void operator!=( scoped_ptr const& ) const; // 不等函数私有化 public: typedef T element_type; /* 接受一个类型为T*的指针p,创建出一个scoped_ptr对象,并在内部参数保存指针参数p p必须是一个new表达式动态分配的结果,或者一个空指针nullptr 当scoped_ptr对象的生命期结束时,析构函数对使用delete操作符自动销毁所保存的对象,从而正确的回收资源 */ explicit scoped_ptr( T * p = 0 ): px( p ) // never throws // 显式构造函数 { } explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT : px( p.release() ) { } ~scoped_ptr() // never throws // 析构函数 { boost::checked_delete( px ); } // 重置scoped_ptr, 它删除了原来保存的指针,再保存新的指针值p。 // 如果p是空指针,那么scoped_ptr将不再持有任何指针 // 一般情况下,reset不应该被调用,因为它违背了scoped_ptr的本意--》资源应该一直由scoped+pty自己自动管理。 void reset(T * p = 0) // never throws // 重置智能指针 { BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors this_type(p).swap(*this); } // * 和->重载的意义: 模仿被代理的指针的行为 T & operator*() const // never throws //操作符重载 { BOOST_ASSERT( px != 0 ); // BOOST_ASSERT仅工作再debug模式下 return *px; } T * operator->() const // never throws //操作符重载 { BOOST_ASSERT( px != 0 ); return px; } // 用在某些要求必须是原始指针的场景,但是使用时必须小写,这将使原始指针脱离scoped_ptr的控制,不能对这个指针delete T * get() const BOOST_NOEXCEPT // 获取原始指针 { return px; } // implicit conversion to "bool" #include <boost/smart_ptr/detail/operator_bool.hpp> void swap(scoped_ptr & b) BOOST_NOEXCEPT { T * tmp = b.px; b.px = px; px = tmp; } }; ## unique\_ptr ## std::unique\_pt形如起名,是一种独占的智能指针,与所指对象的内存绑定紧密, 禁止其他智能指针与其共享**同一个**对象,从而导致代码的安全: auto pointer = std::make_unique<int>(10); //C++14引入 auto p1 = pointer; // 非法 既然是独占,也就是说不可复制。但是,我们可以利用std::move将其转移给其他unique\_ptr,一旦被转移,就不再拥有,除非被显示的归还。 例如: #include <iostream> #include <memory> struct Foo{ Foo(){ printf("%s", "Foo:Foo\n");} ~Foo(){ printf("%s", "~Foo:Foo\n");} void foo(){ printf("%s", "====Foo:foo\n");} }; void f(const Foo &){ printf(" f(const Foo &)\n"); } int main(){ std::unique_ptr<Foo> p1(std::make_unique<Foo>()); // p1不空,输出 if (p1){ p1->foo(); } { std::unique_ptr<Foo> p2(std::move(p1)); f(*p2); // f(const Foo &) if(p2){ p2->foo(); // ====Foo:foo } if (p1){ p1->foo(); // std::move给了p2,因此无输出 } p1 = std::move(p2); if(p2){ p2->foo(); // std::move给了p1,因此无输出 } if (p1){ p1->foo(); // ====Foo:foo } p2 = std::move(p1); printf("p2被销毁\n"); } if (p1){ p1->foo(); // ====Foo:foo } return 0; } 从实现上来讲,`unique_ptr`是一个删除了拷贝构造函数、保留了移动构造函数的指针封装类型。程序员仅可以使用右值对unique\_ptr对象进行构造,而且一旦构造成功,右值对象中的指针即被“窃取”,因此,该右值对象马上失去了对指针的“所有权” ## shared\_ptr ## * `shared_ptr`是一个最像指针的智能支持,被c++11标准收入 * `shared_ptr`是一种智能指针,它能够记录多少个shared\_ptr共同指向同一个对象,从而消除显式调用的delete,当引用计数变成0时就会将对象自动删除 * `shared_ptr`与`scoped_ptr`一样,包装了new操作符在堆上分配的动态对象,但它实现的是引用计数性的智能指针,可以被自由的拷贝和赋值,在任意地方共享它,当没有代码使用(引用计数为0)它时才被包装的动态分配的对象释放 * 因为使用`std::shared_ptr`仍然需要new来调用,这使得代码出现了不对称,`std::make_shared`就能够消除显式的调用new,所以std::make\_shared会分配创建传入参数中的对象,并返回这个对象类型的std::shared\_ptr指针 ### 实践 ### #include <memory> #include <assert.h> #include <map> #include <iostream> using namespace std; class Shape{ public: Shape() { cout<<"Base::Draw()"<<endl; } ~Shape() { cout<<"Base::Erase()"<<endl; } }; class Polygon:public Shape{ public: Polygon() { cout<<"Polygon::Draw()"<<endl;} ~Polygon() { cout<<"Polygon Erase()"<<endl;} }; class Rectangle:public Polygon{ public: Rectangle() { cout<<"Rectangle::Draw()"<<endl;} ~Rectangle() { cout<<"Rectangle Erase()"<<endl;} }; ### 初始化 ### { //一般的初始化方式 shared_ptr<string> pint_s(new string("normal usage!")); cout<<*pint_s<<endl; //推荐的安全的初始化方式 shared_ptr<string> pint_s1 = make_shared<string>("safe uage!"); cout<<*pint_s1<<endl; } ![在这里插入图片描述][20210207170908849.png] { //一般的初始化方式(不推荐) shared_ptr<Shape> pint(new Shape()); } { //推荐的安全的初始化方式 shared_ptr<Shape> pint1 = make_shared<Shape>(); } { // 变量类型太长,可以重命名(推荐) typedef shared_ptr<Shape> sp_t; sp_t sp2 = make_shared<Shape>(); } ![在这里插入图片描述][20210207171120819.png] typedef shared_ptr<Shape> sp_t; sp_t sp1 = make_shared<Polygon>(); ![在这里插入图片描述][20210207171204556.png] 托管指针 int main6() { shared_ptr<Shape> sp1= make_shared<Shape>(); } // Base::Draw() ---> 由sp1托管 shared_ptr<Shape> sp2(sp1); // 同时交由sp2托管 shared_ptr<Shape> sp3; sp3 = sp1; // 同时交由sp3托管 // 注意,不能用下面的方式使得两个 shared_ptr 对象托管同一个指针: /* Shape* p = new Shape; shared_ptr <Shape> sp4(p), sp5(p); sp4 和 sp5 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp5 无法知道 p 已经被 sp4 托管过)。这样,当 sp4 消亡时要析构 p,sp5 消亡时要再次析构 p,这会导致程序崩溃。 */ return 0; } 智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空 #include <iostream> #include <memory> using namespace std; int main() { /*---------空指针------------*/ shared_ptr<string> p1; if(!p1) //!默认初始化的智能指针中保存着一个空指针!并不是""空字符串 cout<<"p1==NULL"<<endl; /*---------初始化------------*/ shared_ptr<string> p2(new string); if(p2&&p2->empty()){ //!需要注意的时empty时属于string的成员函数。 *p2="helloworld"; cout<<*p2<<endl; } shared_ptr<int> spi(new int); // 一个int类型的shared_ptr assert(spi); // bool类型转换判断指针的有效性 *spi = 256; shared_ptr<std::string> sps(new std::string("smart")); assert(sps->size() == 5); // shared_ptr<int> pa = new int(1);//!error:不允许以暴露裸漏的指针进行赋值操作。 //一般的初始化方式 shared_ptr<std::exception> sp1(new std::bad_exception()); auto sp2 = dynamic_pointer_cast<std::bad_exception>(sp1); auto sp3 = static_pointer_cast<std::exception>(sp2); assert(sp3 == sp1); /**********************不推荐***********************/ int * p = new int(32); shared_ptr<int> pp(p); cout<<*pp<<endl; /*意外的情况*/ // delete p; //!不小心把delete掉了。 // cout<<*pp<<endl;· //!pp也不再有效。 } ### 关于get()函数 ### 智能指针定义了一个名为get的函数,它返回一个内置指针,指向智能指针的管理的对象。此函数设置的初衷是当我们向不能使用智能指针的代码传递一个内置指针。使用get返回指针的代码不能delete此指针。 useShared_ptr(p1.get()); // delePointer(p1.get()); //!error: 也就是说,std::shared\_ptr可以通过`get()`方法来获取原始指针,通过`reset()`来减少一个引用计数,并通过使用`use_count`来查看一个对象的引用计数 #include <iostream> #include <memory> int main(){ auto pointer = std::make_shared<int>(10); auto pointer2 = pointer; // 引用计数 +1 auto pointer3 = pointer; // 引用计数 +1 int *p = pointer.get(); // 这样不会增加引用计数 std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3 pointer2.reset(); std::cout << "reset pointer2:" << std::endl; std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2 pointer3.reset(); std::cout << "reset pointer3:" << std::endl; std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; return 0; } 再次声明: * get用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get。 * 特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值! ### 关于make\_shared函数 ### 最安全的分配和使用动态内存的方法是调用一个名为make\_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回此对象的shared\_ptr。与智能指针一样,make\_shared也定义在头文件memory中。 #include <iostream> using namespace std; int main() { shared_ptr<int> p3 = make_shared<int>(42); cout<<*p3<<endl; shared_ptr<string> pstr = make_shared<string>("99999"); cout<<*pstr<<endl; shared_ptr<int> pint = make_shared<int>(); //!默认初始化为 0 cout<<*pint<<endl; auto pau = make_shared<string>("auto"); //!更简单,更常用的方式。 cout<<*pau<<endl; } 使用make\_shared用其参数来构造给定类型的对象;传递的参数必须能够与该类型的某个构造函数相匹配。 通常我们用auto来定义一个对象来保存make\_shared的结果,这种方式更为简单。 ### shared\_ptr的拷贝和赋值 ### 当进行拷贝或者赋值操作时,每个shared\_ptr都会记录有多少个其他的shared\_ptr指向相同的对象: #include <iostream> #include <memory> using namespace std; int main() { auto p = make_shared<int>(42); //!p指向的对象只有p一个引用者。 cout<<p.use_count()<<endl; // 1 //返回与p共享对象的智能指针数量;可能很慢,主要用于调试。 cout<<p.unique()<<endl; // 若p.use_count()为1,则返回 true;否则返回 false auto q(p); //!p和q指向相同的对象,此对象有两个引用者。 cout<<p.use_count()<<endl; // 2 cout<<q.use_count()<<endl; // 2 return 0; } ### shared\_ptr作返回值 ### #include <iostream> #include <memory> using namespace std; shared_ptr<string> factory(const char * p){ return make_shared<string>(p); } void user_factory(){ shared_ptr<string> p = factory("helloworld"); // p和return返回的时同一个对象 cout<<p.use_count() <<endl; //1 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() { user_factory(); auto p = return_share_ptr(); cout<<p.use_count()<<endl; // 1 return 0; } ### 容器中的shared\_ptr ### 对于一块内存,shared\_ptr类保证了只要有share\_ptr对象引用它,他就不会被释放掉。由于这个特性,保证`shared_ptr`在不用之后不再保留就非常重要了,通常这个过程能够自动执行而不需要人工干预。有一种例外是我们将shared\_ptr放在了容器中。所以永远不要忘记erease不用的shared\_ptr #include <iostream> #include <memory> #include <list> #include <map> using namespace std; int main() { map<string, shared_ptr<string>> mapptr; mapptr["aaaa"] = make_shared<string>("11111"); mapptr["bbbb"] = make_shared<string>("2222"); mapptr.erase("aaaa"); std::cout << mapptr["aaaa"] << "\t"; std::cout << mapptr["bbbb"] << "\n"; list<shared_ptr<string>>pstrList; pstrList.push_back(make_shared<string>("1111")); pstrList.push_back(make_shared<string>("2222")); pstrList.push_back(make_shared<string>("3333")); pstrList.push_back(make_shared<string>("4444")); for(auto itr = pstrList.begin() ;itr!=pstrList.end();){ // erase() 接受一个 iterator 作为入参,可从list或vector中删除该 iterator 所指向的元素。但同时传入的该 iterator 即失效!! if(**itr == "3333"){ itr = pstrList.erase(itr); }else{ itr++; } } cout<<"-------------after remove------------"<<endl; for(auto p:pstrList) { cout<<*p<<endl; } return 0; } ### shared\_ptr对象的销毁 ### 1. 管理动态数组 默认情况下,shared\_ptr指向的动态的内存是使用delete来删除的。这和我们手动去调用delete然后调用对象内部的析构函数是一样的。与unique\_ptr不同,shared\_ptr不直接管理动态数组。如果希望使用shared\_ptr管理一个动态数组,必须提供自定义的删除器来替代delete 。 ### 状态共享 ### 使用shared\_ptr在一个常见的原因是允许多个多个对象共享相同的状态,而非多个对象独立的拷贝! #include <iostream> #include <memory> #include <list> #include <map> using namespace std; void copyCase() { list<string> v1({ "1","b","d"}); list<string> v2 = v1; //!v1==v2占用两段内存 v1.emplace_back("cc"); //!v1!=v2 cout<< "copyCase : "<<v1.size() << "\t" << v2.size() <<endl; } //v1和v2分属两个不同的对象,一个改变不会影响的状态。 // copyCase : 4 3 void shareCase() { shared_ptr<list<string>> v1 = make_shared<list<string>>(2,"bb"); shared_ptr<list<string>> v2 = v1; (*v1).emplace_back("c2c"); cout<<"shareCase : " <<v1->size() << "\t" << v2->size() <<endl; } //v1和v2属于一个对象的两个引用,有引用计数为证,其内容的改变是统一的。 shareCase : 3 3 int main() { copyCase(); cout<<"++++++++++++++++"<<endl; shareCase(); } ### 智能指针与异常 ### 异常发生后,常规的动态内存常常不能正确释放。但是如果使用智能指针,即程序过早结束,智能指针也能确保在内存不需要时将其释放: void f() { shared_ptr<int>sp(new int(42)) ; } 函数的推出,要么有两种情况,正常处理结束或者发生了异常,无论哪种情况,局部对象都会被销毁。在上面的程序中,sp是一个shared\_ptr,因此sp销毁时会检查引用计数。在此例中,sp是指向这块内存的唯一指针。所以会被释放掉。 与之相对的,当发生异常时,我们直接管理的内存时不会自动释放的,如果使用内置指针管理内存,且在new之后对应的delet之前发生异常,则内存不会释放。 void f() { int *p = new int(42); //code//!异常抛出,且没有在f()中被捕获。 delete p; } 如果在new和delete之间发生异常,且异常未在f()中捕获,则内存就永远不会被释放了。 ### 源码 ### template<class T> class shared_ptr { private: // Borland 5.5.1 specific workaround typedef shared_ptr<T> this_type; public: typedef typename boost::detail::sp_element< T >::type element_type; shared_ptr() BOOST_NOEXCEPT : px( 0 ), pn() // never throws in 1.30+ { } #if !defined( BOOST_NO_CXX11_NULLPTR ) shared_ptr( boost::detail::sp_nullptr_t ) BOOST_NOEXCEPT : px( 0 ), pn() // never throws { } #endif template<class Y> explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete { boost::detail::sp_pointer_construct( this, p, pn ); } // // Requirements: D's copy constructor must not throw // // shared_ptr will release p by calling d(p) // template<class Y, class D> shared_ptr( Y * p, D d ): px( p ), pn( p, d ) { boost::detail::sp_deleter_construct( this, p ); } #if !defined( BOOST_NO_CXX11_NULLPTR ) template<class D> shared_ptr( boost::detail::sp_nullptr_t p, D d ): px( p ), pn( p, d ) { } #endif // As above, but with allocator. A's copy constructor shall not throw. template<class Y, class D, class A> shared_ptr( Y * p, D d, A a ): px( p ), pn( p, d, a ) { boost::detail::sp_deleter_construct( this, p ); } #if !defined( BOOST_NO_CXX11_NULLPTR ) template<class D, class A> shared_ptr( boost::detail::sp_nullptr_t p, D d, A a ): px( p ), pn( p, d, a ) { } #endif // generated copy constructor, destructor are fine... #if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES ) // ... except in C++0x, move disables the implicit copy shared_ptr( shared_ptr const & r ) BOOST_NOEXCEPT : px( r.px ), pn( r.pn ) { } #endif template<class Y> explicit shared_ptr( weak_ptr<Y> const & r ): pn( r.pn ) // may throw { boost::detail::sp_assert_convertible< Y, T >(); // it is now safe to copy r.px, as pn(r.pn) did not throw px = r.px; } template<class Y> shared_ptr( weak_ptr<Y> const & r, boost::detail::sp_nothrow_tag ) BOOST_NOEXCEPT : px( 0 ), pn( r.pn, boost::detail::sp_nothrow_tag() ) { if( !pn.empty() ) { px = r.px; } } template<class Y> #if !defined( BOOST_SP_NO_SP_CONVERTIBLE ) shared_ptr( shared_ptr<Y> const & r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() ) #else shared_ptr( shared_ptr<Y> const & r ) #endif BOOST_NOEXCEPT : px( r.px ), pn( r.pn ) { boost::detail::sp_assert_convertible< Y, T >(); } // aliasing template< class Y > shared_ptr( shared_ptr<Y> const & r, element_type * p ) BOOST_NOEXCEPT : px( p ), pn( r.pn ) { } #ifndef BOOST_NO_AUTO_PTR template<class Y> explicit shared_ptr( std::auto_ptr<Y> & r ): px(r.get()), pn() { boost::detail::sp_assert_convertible< Y, T >(); Y * tmp = r.get(); pn = boost::detail::shared_count( r ); boost::detail::sp_deleter_construct( this, tmp ); } #if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES ) template<class Y> shared_ptr( std::auto_ptr<Y> && r ): px(r.get()), pn() { boost::detail::sp_assert_convertible< Y, T >(); Y * tmp = r.get(); pn = boost::detail::shared_count( r ); boost::detail::sp_deleter_construct( this, tmp ); } #elif !defined( BOOST_NO_SFINAE ) && !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) template<class Ap> explicit shared_ptr( Ap r, typename boost::detail::sp_enable_if_auto_ptr<Ap, int>::type = 0 ): px( r.get() ), pn() { typedef typename Ap::element_type Y; boost::detail::sp_assert_convertible< Y, T >(); Y * tmp = r.get(); pn = boost::detail::shared_count( r ); boost::detail::sp_deleter_construct( this, tmp ); } #endif // BOOST_NO_SFINAE, BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION #endif // BOOST_NO_AUTO_PTR #if !defined( BOOST_NO_CXX11_SMART_PTR ) template< class Y, class D > shared_ptr( std::unique_ptr< Y, D > && r ): px( r.get() ), pn() { boost::detail::sp_assert_convertible< Y, T >(); typename std::unique_ptr< Y, D >::pointer tmp = r.get(); pn = boost::detail::shared_count( r ); boost::detail::sp_deleter_construct( this, tmp ); } #endif // assignment shared_ptr & operator=( shared_ptr const & r ) BOOST_NOEXCEPT { this_type(r).swap(*this); return *this; } #if !defined(BOOST_MSVC) || (BOOST_MSVC >= 1400) template<class Y> shared_ptr & operator=(shared_ptr<Y> const & r) BOOST_NOEXCEPT { this_type(r).swap(*this); return *this; } #endif #ifndef BOOST_NO_AUTO_PTR template<class Y> shared_ptr & operator=( std::auto_ptr<Y> & r ) { this_type( r ).swap( *this ); return *this; } #if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES ) template<class Y> shared_ptr & operator=( std::auto_ptr<Y> && r ) { this_type( static_cast< std::auto_ptr<Y> && >( r ) ).swap( *this ); return *this; } #elif !defined( BOOST_NO_SFINAE ) && !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) template<class Ap> typename boost::detail::sp_enable_if_auto_ptr< Ap, shared_ptr & >::type operator=( Ap r ) { this_type( r ).swap( *this ); return *this; } #endif // BOOST_NO_SFINAE, BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION #endif // BOOST_NO_AUTO_PTR #if !defined( BOOST_NO_CXX11_SMART_PTR ) template<class Y, class D> shared_ptr & operator=( std::unique_ptr<Y, D> && r ) { this_type( static_cast< std::unique_ptr<Y, D> && >( r ) ).swap(*this); return *this; } #endif // Move support #if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES ) shared_ptr( shared_ptr && r ) BOOST_NOEXCEPT : px( r.px ), pn() { pn.swap( r.pn ); r.px = 0; } template<class Y> #if !defined( BOOST_SP_NO_SP_CONVERTIBLE ) shared_ptr( shared_ptr<Y> && r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() ) #else shared_ptr( shared_ptr<Y> && r ) #endif BOOST_NOEXCEPT : px( r.px ), pn() { boost::detail::sp_assert_convertible< Y, T >(); pn.swap( r.pn ); r.px = 0; } shared_ptr & operator=( shared_ptr && r ) BOOST_NOEXCEPT { this_type( static_cast< shared_ptr && >( r ) ).swap( *this ); return *this; } template<class Y> shared_ptr & operator=( shared_ptr<Y> && r ) BOOST_NOEXCEPT { this_type( static_cast< shared_ptr<Y> && >( r ) ).swap( *this ); return *this; } #endif #if !defined( BOOST_NO_CXX11_NULLPTR ) shared_ptr & operator=( boost::detail::sp_nullptr_t ) BOOST_NOEXCEPT // never throws { this_type().swap(*this); return *this; } #endif void reset() BOOST_NOEXCEPT // never throws in 1.30+ { this_type().swap(*this); } template<class Y> void reset( Y * p ) // Y must be complete { BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors this_type( p ).swap( *this ); } template<class Y, class D> void reset( Y * p, D d ) { this_type( p, d ).swap( *this ); } template<class Y, class D, class A> void reset( Y * p, D d, A a ) { this_type( p, d, a ).swap( *this ); } template<class Y> void reset( shared_ptr<Y> const & r, element_type * p ) { this_type( r, p ).swap( *this ); } // never throws (but has a BOOST_ASSERT in it, so not marked with BOOST_NOEXCEPT) typename boost::detail::sp_dereference< T >::type operator* () const { BOOST_ASSERT( px != 0 ); return *px; } // never throws (but has a BOOST_ASSERT in it, so not marked with BOOST_NOEXCEPT) typename boost::detail::sp_member_access< T >::type operator-> () const { BOOST_ASSERT( px != 0 ); return px; } // never throws (but has a BOOST_ASSERT in it, so not marked with BOOST_NOEXCEPT) typename boost::detail::sp_array_access< T >::type operator[] ( std::ptrdiff_t i ) const { BOOST_ASSERT( px != 0 ); BOOST_ASSERT( i >= 0 && ( i < boost::detail::sp_extent< T >::value || boost::detail::sp_extent< T >::value == 0 ) ); return px[ i ]; } element_type * get() const BOOST_NOEXCEPT { return px; } // implicit conversion to "bool" #include <boost/smart_ptr/detail/operator_bool.hpp> bool unique() const BOOST_NOEXCEPT { return pn.unique(); } long use_count() const BOOST_NOEXCEPT { return pn.use_count(); } void swap( shared_ptr & other ) BOOST_NOEXCEPT { std::swap(px, other.px); pn.swap(other.pn); } template<class Y> bool owner_before( shared_ptr<Y> const & rhs ) const BOOST_NOEXCEPT { return pn < rhs.pn; } template<class Y> bool owner_before( weak_ptr<Y> const & rhs ) const BOOST_NOEXCEPT { return pn < rhs.pn; } void * _internal_get_deleter( boost::detail::sp_typeinfo const & ti ) const BOOST_NOEXCEPT { return pn.get_deleter( ti ); } void * _internal_get_untyped_deleter() const BOOST_NOEXCEPT { return pn.get_untyped_deleter(); } bool _internal_equiv( shared_ptr const & r ) const BOOST_NOEXCEPT { return px == r.px && pn == r.pn; } // Tasteless as this may seem, making all members public allows member templates // to work in the absence of member template friends. (Matthew Langston) #ifndef BOOST_NO_MEMBER_TEMPLATE_FRIENDS private: template<class Y> friend class shared_ptr; template<class Y> friend class weak_ptr; #endif element_type * px; // contained pointer boost::detail::shared_count pn; // reference counter }; // shared_ptr ## weak\_ptr ## > weak\_ptr是为了配合shared\_ptr而引入的一种智能指针,它更像是`shared_ptr`的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载`operator *`和`->`。它的最大作用在于协助`shared_ptr`工作,像旁观者那样观测资源的使用情况 std::shared\_ptr还是会存在着资源无法释放的问题,如下: #include <iostream> #include <memory> struct A; struct B; struct A { std::shared_ptr<B> pointer; ~A() { std::cout << "A 被销毁" << std::endl; } }; struct B { std::shared_ptr<A> pointer; ~B() { std::cout << "B 被销毁" << std::endl; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->pointer = b; b->pointer = a; } 运行结果是A,B都不会销毁,这是因为a, b内部的pointer同时又引用了a, b,这使得a, b的引用计数均变成了2,而离开作用域时,却只能造成这块区域的引用计数减1,这样就导致了a, b对象指向的内存区域引用计数不为0,而外部已经没有办法找到这块区域了,也就造成了内存泄漏,如下图: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1] 解决这个问题的办法是使用弱引用指针std::weak\_pttr,std::weak\_ptr是一种弱引用(std::shared\_ptr可以看成是强引用)。弱引用不会引起引用计数增加,当换用弱引用的时候,最终的释放流程如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 2] 在上图中,最后一步只剩下B,而B并没有任何智能指针引用它,因此这块内存资源也会被释放。 std::weak\_ptr没有\*运算符和->运算符,所以不能够对资源进行操作,它的唯一作用就是用于检查std::shared\_ptr是否存在,其expired()方法能在资源未被释放时,返回false,否则返回true ### 用法 ### weak\_ptr被设计为与shared\_ptr协同工作,可以从一个`shared_ptr`或者另一个`weak_ptr`对象构造,获得资源的观测权。但weak\_ptr\`没有共享资源,它的构造不会引起指针引用计数的增加或者减少,它只是一个静静的观察者 [参考][Link 1] [20201106162110563.png_pic_center]: /images/20221120/a089a23119cb4766bee8552f2cd66d3d.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/20210207173031630.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg==,size_16,color_FFFFFF,t_70 [20210207173045543.png]: /images/20221120/af3a12d225be49ba9776af224fccc9cd.png [2020110616561647.png_pic_center]: https://img-blog.csdnimg.cn/2020110616561647.png#pic_center [20210207170908849.png]: https://img-blog.csdnimg.cn/20210207170908849.png [20210207171120819.png]: https://img-blog.csdnimg.cn/20210207171120819.png [20210207171204556.png]: https://img-blog.csdnimg.cn/20210207171204556.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20210418184410366.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/20210418185142637.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg==,size_16,color_FFFFFF,t_70 [Link 1]: https://www.cnblogs.com/wangkeqin/p/9351191.html
相关 智能指针 C++里面的四个智能指针分别是:auto\_ptr,shared\_ptr,weak\_ptr,unique\_ptr,其中后三个是C++11支持的,第一个auto\_ptr已 客官°小女子只卖身不卖艺/ 2023年10月09日 12:14/ 0 赞/ 157 阅读
相关 智能指针 智能指针 1.引入使用计数 定义智能指针的通用技术是采用一个使用计数。智能指针将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为0时 Bertha 。/ 2023年06月23日 15:29/ 0 赞/ 109 阅读
相关 智能指针 智能指针 智能指针的智能,体现在智能指针能够在每次完成工作之后自动释放占用的空间。 我们通常使用的裸指针,使用完成后需要通过delete或者free来进行释放,那么如 向右看齐/ 2023年06月21日 03:46/ 0 赞/ 42 阅读
相关 智能指针 有三个智能指针,auto\_ptr(弃用)、unique\_ptr、shared\_ptr. 智能指针可以自动跟踪动态内存,在其过期时自动释放。 include< ゝ一纸荒年。/ 2023年02月21日 14:05/ 0 赞/ 142 阅读
相关 智能指针 class MyClass { public: MyClass(int a){}; ~MyClass() 逃离我推掉我的手/ 2023年01月03日 12:54/ 0 赞/ 282 阅读
相关 C/C++编程:智能指针 > 计算机系统中的资源有很多种,内存是我们最常用到的,此外还有文件描述符,socket、操作系统handler,数据库连接等,程序里申请这些资源之后必须及时归坏系统,否则就会产 ╰半夏微凉°/ 2022年12月19日 09:18/ 0 赞/ 212 阅读
相关 智能指针 scoped\_ptr解析 《超越C++标准库-Boost库导论》 头文件: "boost/scoped\_ptr.hpp" boost::scoped\_ptr 用于确 超、凢脫俗/ 2022年08月09日 16:37/ 0 赞/ 350 阅读
相关 智能指针 RAII(Resource Acquisition Is Initialization): 资源分配即初始化,定义封装一个类,用来实现调用构造函数时就可完成资源的分 梦里梦外;/ 2022年07月17日 03:43/ 0 赞/ 320 阅读
相关 智能指针 在C++中,如果指针使用不当,比如没有及时释放指针所指向的内存,或者野指针等,会造成系统发生不可预估的错误,为了防止这一情况的发生,C++ STL提供了一系列智能指针类型 智 偏执的太偏执、/ 2022年05月17日 07:29/ 0 赞/ 359 阅读
相关 智能指针 智能指针 在java中如果在堆上开辟内存是不需要手动释放的,我们叫做智能指针;但是在C++中如果用new在堆上开辟了空间,我们需要用delete进行手动释放,否则造 悠悠/ 2022年03月18日 11:54/ 0 赞/ 384 阅读
还没有评论,来说两句吧...