Boost 智能指针(一)
2017-11-15 23:37
393 查看
引言:这是学习过程中的智能指针笔记,代码的解释写在了注释之间,为了方便及时查阅,以供大家学习。
内容包含:
1、初步认识 shared_ptr 的引用计数
2、shared_ptr 的缺陷 以及 如何使用 weak_ptr 避免缺陷
3、shared_ptr 的使用技巧
1、主函数
介绍了shared_ptr 的引用计数,shared_ptr 的缺陷 及使用技巧
主函数中使用到的 IPrinter类
IPrinter.h
IPrinter.cpp
主函数中使用到的 FileSharedPtr 类
FileSharedPtr.h
FileSharedPtr.cpp
2、使用 weak_ptr 解决 shared_ptr 循环引用导致内存泄露问题
本文示例源码(VS2015):http://pan.baidu.com/s/1qYga9LM
内容包含:
1、初步认识 shared_ptr 的引用计数
2、shared_ptr 的缺陷 以及 如何使用 weak_ptr 避免缺陷
3、shared_ptr 的使用技巧
1、主函数
介绍了shared_ptr 的引用计数,shared_ptr 的缺陷 及使用技巧
#define _CRT_SECURE_NO_WARNINGS #include <boost/shared_ptr.hpp> #include <iostream> #include <string> #include <assert.h> #include <vector> #include "FileSharedPtr.h" #include "IPrinter.h" using namespace std; class Foo { public: Foo(const string & strname):m_strName(strname){} ~Foo() { cout << "Destructing Foo with name = " << m_strName << endl; } private: string m_strName; }; typedef boost::shared_ptr<Foo> FooPtr; //---------------- shared_ptr 的引用计数----------------------// void Test_Boost_Shared_Ptr() { /// ptr1 获得 Foo1 指针的所有权 FooPtr ptr1(new Foo("Foo1"));/// 引用计数为1 assert(ptr1.use_count() == 1); /// ptr2 指向 ptr1 FooPtr ptr2 = ptr1;/// 调用 shared_ptr 的赋值操作符,引用计数加1 assert(ptr1.use_count() == ptr2.use_count());/// 两者引用计数相同 assert(ptr1 == ptr2);/// shared_ptr 重载 == 操作符,等同于ptr1.get() == ptr2.get() assert(ptr1.use_count() == 2);/// 现在ptr1 和ptr2 都指向了Foo1,因此计数器都为2 /// ptr3获得 Foo1 指针的所有权 FooPtr ptr3 = ptr2;///引用计数加1 assert(ptr1.use_count() == ptr2.use_count() && ptr1.use_count() == ptr3.use_count()); assert(ptr1.use_count() == 3 && ptr2.use_count() == 3 && ptr3.use_count() == 3); assert(ptr1 == ptr2 && ptr1 == ptr3); /// 重置ptr3,测试reset()函数 ptr3.reset(); assert(ptr3.use_count() == 0 && ptr3.get() == NULL); cout << "ptr3 引用计数为0,get()指针指向NULL,但是不会调用析构函数,因为ptr1和ptr2都指向了原生指针" << endl; assert(ptr1.use_count() == ptr2.use_count() && ptr1.use_count() == 2); assert(ptr1 == ptr2 && ptr1 != ptr3); /// 前面ptr1和ptr2都指向了同一个对象,他们的引用计数都为2,现在创建一个ptr4,让其指向ptr1 FooPtr ptr4 = ptr1; // 引用计数加一 assert(ptr1 == ptr2 && ptr4 == ptr1); assert(ptr4.use_count() == 3 && ptr1.use_count() == 3 && ptr2.use_count() == 3); /// 现在转移ptr2的所有权到另一个Foo指针上去 cout << "转移ptr2的拥有权到一个新的Foo指针上" << endl; ptr2.reset(new Foo("Foo2")); /// 在ptr2转移所有权后,ptr2计数器应该是1,并且ptr1和ptr4的计数器是2,并且ptr1 != ptr2 assert(ptr2.use_count() == 1 && ptr1.use_count() == 2 && ptr4.use_count() == 2); assert(ptr1 != ptr2 && ptr1 == ptr4); ///此时ptr3因为被重置为0,并且get()为NULL了,我们再使用一个ptr5来指向ptr3也应该引用计数为0 FooPtr ptr5 = ptr3; assert(ptr5.use_count() == 0 && ptr5.get() == NULL); /*********************** 总结 ******************************** 运行到此时,程序即将结束,在退出作用于前会调用析构函数(引用计数为0,即调用析构函数) 首先会打印出Destructing a Foo with name = Foo2 ptr1 和 ptr4也会相继调用析构函数进行引用计数递减 最终会打印出Destructing a Foo withname = Foo1 内存释放完毕,这样就没有内存泄漏了 **************************************************************/ } //---------------------shared_ptr 的缺陷------------------------// void Test_Boost_Shared_ptr_crash() { Foo * pFoo = new Foo("Foo1"); shared_ptr<Foo> ptr1(pFoo); shared_ptr<Foo> ptr2(pFoo); cout << ptr1.use_count() << endl; cout << ptr2.use_count() << endl; /// 函数退出时会崩溃!,因为ptr1、ptr2两次释放同一内存而破坏堆,导致程序崩溃 /******************** 总结 ****************************** shared_ptr 多次引用同一内存数据将导致程序崩溃 1)直接使用 boost::shared_ptr<int> ptr1(new int(100)); 使用匿名内存分配限制其他shared_ptr多次指向同一内存数据 多次指向时使用 shared_ptr 的复制操作符或拷贝构造函数,例如 shared_ptr<int> ptr2 = ptr1; 上面的匿名方式就是资源初始化既分配技术,直接在构造函数中分配内存 2)使用boost的 make_shared 模板函数,例如shared_ptr<int> ptr1 = boost::make_shared<int>(100) shared_ptr<int> ptr2 = ptr1; ****************************************************/ } //----------------------shared_ptr 的使用技巧--------------------------// /// shared_ptr 的使用技巧一:将shared_ptr用于标准容器库 /// (1)标准容器库作为shared_ptr管理的对象 (2)将 shared_ptr 作为容器的元素 void Test_Vector_Shared_Ptr() { char name[32]; vector<FooPtr> ptrs; for (int i = 0; i < 20; i++) { sprintf(name, "foo%d", i); ptrs.push_back(FooPtr(new Foo(name))); }/// 循环完毕:20个FooPtr的引用计数为 1 for (int i = 0; i < 20; i++) { FooPtr ptr = ptrs[i]; /// 此时 ptr进行引用后,引用计数加1,此时引用计数为 2 assert(ptrs[i].use_count() == 2); }/// 循环完毕,各自的引用计数为1.为什么不是2?因为 ptr 为局部变量,它的作用域 /// 在循环内,循环完毕退出作用域的时候,会进行减1操作,所以 2-1=1 for (int i = 0; i < 20; i++) { assert(ptrs[i].use_count() == 1); } }/// 函数结束的时候,退出作用域,ptrs里面的FooPtr的引用计数会进行减1操作,最后的引用计数为0 /// 引用计数为0后,自动调用析构函数释放内存 /// shared_ptr 的使用技巧二:以函数封住现有的c函数 typedef boost::shared_ptr<FILE> FilePtr; void FileClose(FILE * f) { fclose(f); cout << "调用fclose函数释放FILE资源" << endl; } FilePtr FileOpen(char const* path, char const* mode) { //第二个参数是指,通过调用 FileClose 来释放第一个参数 FilePtr fptr(fopen(path, mode), FileClose); //FilePtr fptr(fopen(path, mode), fclose); return fptr; } void FileRead(FilePtr& f, void* data, size_t size) { cout << "use_count() = " << f.use_count() << endl; fread(data, 1, size, f.get()); } void Test_C_File_Ptr() { FilePtr ptr = FileOpen("memory.log", "r"); FilePtr ptr2 = ptr; char data[512] = { 0 }; FileRead(ptr, data, 512); cout << data << endl; } /// shared_ptr 使用技巧三:用c++桥接设计模式来封装现有的c函数 void Test_FileSharePtr() { char data[512] = { 0 }; FileSharedPtr pPile("memory.log", "r"); pPile.Read(data, 512); } /// shared_ptr 使用技巧四:使用面向接口编程方式隐藏实现 /// 注意面向接口编程的思想 void Test_Printer() { PrinterPtr ptr = CreatePrinter(); ptr->Print(); PrinterPtr ptr2 = ptr; ptr2->Print(); } int main() { //Test_Boost_Shared_Ptr(); //Test_Boost_Shared_ptr_crash(); //Test_Vector_Shared_Ptr(); //Test_C_File_Ptr(); //Test_FileSharePtr(); Test_Printer(); getchar(); return 0; }
主函数中使用到的 IPrinter类
IPrinter.h
#pragma once #include <boost/shared_ptr.hpp> class IPrinter { public: virtual void Print() = 0; protected: // 受保护的析构函数, // 如果要调用析构函数,必须被继承,也不会被 delete 释放,可以防止用户不小心d elete virtual ~IPrinter() { printf("invoke IPrinter virtual析构函数\n"); } }; typedef boost::shared_ptr<IPrinter> PrinterPtr; PrinterPtr CreatePrinter(); // 工厂方法,创建IPrinter智能指针
IPrinter.cpp
#include "IPrinter.h" class Printer :public IPrinter { private: FILE* f; public: Printer(const char* path, const char* mode) { f = fopen(path, mode); } ~Printer() { int result = fclose(f); printf("invoke Printer virtual析构函数:%d\n",result); } virtual void Print() override { char data[512] = { 0 }; fread(data, 1, 512, f); printf("%s\n", data); } // 子类释放的时候,会自动调用父类的析构函数 // 通过这种封装,同时就封装了 delete 的操作,只有调用我们的智能指针才能释放内存 // 此时就实现了,通过智能指针来管理内存的功能,没有了原始指针的操作,也不会忘记没有释放指针导致的内存泄漏问题 }; PrinterPtr CreatePrinter() { PrinterPtr ptr(new Printer("memory.log", "r")); return ptr; }
主函数中使用到的 FileSharedPtr 类
FileSharedPtr.h
#pragma once #include <boost/shared_ptr.hpp> /// shared_ptr 使用技巧三:用c++桥接设计模式来封装现有的c函数 /// 注意桥接模式的用法 class FileSharedPtr { public: FileSharedPtr(); ~FileSharedPtr(); FileSharedPtr(char const* name, char const* mode); void Read(void* data, size_t size); private: class impl;// 很重要一点,前向声明实现该类,具体实现在.cpp文件中,隐藏实现细节 boost::shared_ptr<impl> pimpl;// shared_ptr 作为私有成员变量 };
FileSharedPtr.cpp
#include "FileSharedPtr.h" class FileSharedPtr::impl { private: impl(impl const &) {} impl & operator= (impl const&) {} FILE* f; public: impl(char const* name, char const* mode) { f = fopen(name, mode); } ~impl() { int result = fclose(f); printf("invoke FileSharedPtr::impl 析构函数 result = %d\n", result); } void read(void * data, size_t size) { fread(data, 1, size, f); } }; FileSharedPtr::FileSharedPtr() { } FileSharedPtr::~FileSharedPtr() { } FileSharedPtr::FileSharedPtr(char const* name, char const* mode) : pimpl(new FileSharedPtr::impl(name, mode)) {} void FileSharedPtr::Read(void* data, size_t size) { pimpl->read(data, size); } /**************************************** 这样的好处是: 上层类(FileSharedPtr)都是调用的实现类(impl)的具体的方法, 这样我们就把我们所有的实现都放在实现类中,上层类相当于一个接口类, 不进行相关操作,只提供相应的接口 *******************************************/
2、使用 weak_ptr 解决 shared_ptr 循环引用导致内存泄露问题
#include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <iostream> using namespace std; class Parent; class Child; class Parent { public: Parent(){} ~Parent(){cout << "父类析构函数被调用" << endl;} public: boost::shared_ptr<Child> child_shared_ptr; boost::weak_ptr<Child> child_weak_ptr; }; class Child { public: Child(){} ~Child(){cout << "调用child的析构函数" << endl;} public: boost::shared_ptr<Parent> parent_shared_ptr; boost::weak_ptr<Parent> parent_weak_ptr; }; /// 测试 shared_ptr 循环引用导致内存泄露 void Test_Parent_Child_Ref() { boost::shared_ptr<Parent> father(new Parent()); boost::shared_ptr<Child> son(new Child()); father->child_shared_ptr = son; son->parent_shared_ptr = father; /***************************************************************** 测试结果:会发现并没有调用相关的构造函数,导致内存泄露 原因:因为循环引用,导致father、son的引用计数都为2,退出作用域(就是退出此函数)的时候, 引用计数都减一,此时彼此的引用计数为2-1=1,因为调用析构函数必须的引用计数为0, 所以此时就无法调用析构函数,于是造成father和son所指向的内存得不到释放,导致内存泄露 *****************************************************************/ } /// 测试 weak_ptr 解决循环引用和自引用导致的内存泄漏问题 void Test_Weak_ptr() { boost::shared_ptr<Parent> father(new Parent()); boost::shared_ptr<Child> son(new Child()); father->child_weak_ptr = son; son->parent_weak_ptr = father; // 此时我们使用 lock 方法进行操作 cout << father->child_weak_ptr.lock().use_count() << endl; cout << son->parent_weak_ptr.lock().use_count() << endl; /********************************************************************* lock()方法作用是转换为 shared_ptr: 函数原型: shared_ptr<_Ty> lock() const _NOEXCEPT { return (shared_ptr<_Ty>(*this, false)); } **********************************************************************/ } int main() { Test_Parent_Child_Ref(); getchar(); return 0; } /***************************** 总结 ************************************* 1、引用计数是很便利的内存管理机制,但是对于循环引用或自引用对象(例如链表或树节点)无法管理, 此时用 weak_ptr 来解决这个限制 2、weak_ptr 并不能单独存在,它是与 shared_ptr 同时使用的,它更像 shared_ptr 的助手,而不是 智能指针,因为它不具备智能指针的行为,没有重载 operator* 和 -> 操作符,这是特意的,这样 他就不能共享指针,不能操作资源,这是它弱的原因。它最大的作用是协助 shared_ptr 工作, 像旁观者那样观察资源的使用情况。 3、为什么 weak_ptr 能解决循环引用的问题? weak_ptr(弱引用)获得的是资源的观察权,它可以从一个 shared_ptr 或另一个 weak_ptr 构造, 但 weak_ptr 并没有共享资源,它的构造并不会引起引用计数的增加,同时它的析构也不会引起引用 计数的减少,它 仅仅! 是观察者。 4、weak_ptr 可以被用于标准容器库中的元素 weak_ptr实现了拷贝构造函数和重载了复制操作符,因此weak_ptr可以被用于标准容器中的元素, 例如:在一个树节点中声明子树节点:vector<boost::weak_ptr<Node>> children; ************************************************************************/
本文示例源码(VS2015):http://pan.baidu.com/s/1qYga9LM
相关文章推荐
- boost.smart_ptr-智能指针scoped_ptr
- boost.smart_ptr-智能指针scoped_ptr
- 智能指针的标准之争:Boost vs. Loki
- Boost 库应用——智能指针(一)
- 智能指针的标准之争:Boost vs. Loki(转载)
- boost 智能指针
- boost.smart_ptr-智能指针scoped_ptr
- Boost学习系列2-智能指针(上)
- 建议慎用boost::weak_ptr来避免智能指针循环引用
- boost 02 智能指针管理内存
- [boost-2] 智能指针
- [Boost基础]内存管理——智能指针(一)
- 智能指针的标准之争:Boost vs. Loki
- boost 智能指针
- boost.smart_ptr-智能指针scoped_array
- 智能指针的标准之争:Boost vs. Loki
- boost.smart_ptr-智能指针scoped_array
- boost 智能指针
- 智能指针 boost(scoped_ptr,scoped_array,shared_ptr,shared_array) 和 std (auto_ptr)的比较 .
- boost.smart_ptr-智能指针scoped_ptr