智能指针总结C 智能指针的本质是利用了栈对象在离开作用域时会自动调用析构函数的特性也就是RAII 机制来管理堆内存。这从根本上解决了传统裸指针忘记delete导致的内存泄漏以及异常发生时导致资源未释放的问题。标准的演进主要经历了四个代表性的指针第一std::auto_ptr它最大的问题在于“拷贝语义的控制权转移”。当你把一个auto_ptr赋值给另一个时原指针会被隐式置空。如果后续代码不小心继续使用了原指针就会导致段错误Crash。第二std::unique_ptr现代 C 的首选强调性能与所有权专属所有权。他要求同一时刻只能有一个unique_ptr指向该对象。它禁用了拷贝构造和拷贝赋值必须通过std::move()显式地进行所有权转移。更重要的是在默认情况下它的内存开销和裸指针一模一样属于零成本抽象。一般有俩种实现方式一种是c98里将其拷贝构造函数和赋值运算符重载只声明不定义并且设置为private权限 c11里将拷贝构造函数和赋值运算符重载函数后面跟上delete“尝试引用已经删除的函数”优先并且大量使用unique_ptr第三std::shared_ptr共享与协同强调引用计数与开销核心机制共享所有权。多个指针可以指向同一个对象内部通过引用计数Reference Counting来管理生命周期。当最后一个指向该对象的shared_ptr被销毁时引用计数清零内存才会被释放。底层细节高频考点它除了包含一个指向数据的指针还包含一个指向控制块Control Block的指针存放强/弱引用计数、自定义删除器等。最初使用一个int类型变量计数不能共享、后来使用static int 计数重新定义一个新对象导致计数清零再后来使用线程安全的指针类型变量计数memory库中就是这样实现的适合跨线程共享、生命周期不确定谁活到最后谁负责清理、多个独立组件需要引用同一个底层资源。第四std::weak_ptr幕后协助者强调解决死锁核心机制它是shared_ptr的小弟是一种不控制对象生命周期的“弱引用”。它指向一个由shared_ptr管理的对象但不增加强引用计数只增加弱引用计数。解决的痛点高频考点专门用来解决shared_ptr的循环引用Cyclic Reference问题。比如双向链表中节点互相持有shared_ptr会导致引用计数永远无法清零造成内存泄漏。将其中一个方向改为weak_ptr即可破局。接下来我给您画一下图详细说明一下这个情况一、RAII思想的引入#include iostream using namespace std; #if 0 // C中动态申请的资源需要用户自己手动释放 // 如果操作不当容易造成内存泄漏 // 能否做到让资源自动被释放RAII // RAII : 将资源交给对象管理对象被销毁时自动调用析构函数可以在析构函数中将资源释放掉 templateclass T class smart_ptr { public: smart_ptr(T* ptr nullptr) : _ptr(ptr) {} // 具有指针类似的效果 T operator*() { return *_ptr; } T* operator-() { return _ptr; } ~smart_ptr() { if (_ptr) { delete _ptr; _ptr nullptr; } } T* Get() { return _ptr; } private: T* _ptr; }; void TestSmartPtr1() { //int* p new int(10); //smart_ptrint sp(p); // ... //delete p; smart_ptrint sp(new int(10)); *sp 100; // ... } struct A { int a; int b; int c; }; void TestSmartPtr2() { smart_ptrint sp1(new int(10)); *sp1 100; smart_ptrA sp2(new A()); sp2-a 10; sp2-b 20; sp2-c 30; } // 上述smart_ptr已经很好了达到了刚开始的需求 // 新的问题类中涉及到资源的管理时如果没有显式实现拷贝构造 已经 赋值运算符重载 // 则编译器会以浅拷贝的方式实现smart_ptr就会有问题 // 解决能够深拷贝解决吗 答案不可以 int main() { // TestSmartPtr1(); TestSmartPtr2(); return 0; } #endif二、对于memory库中auto_ptr的使用// 智能指针对原生态指针进行了封装以类的方式管理用户的资源在析构方法中将资源释放掉 // RAII 具有指针类似的行为 如何解决浅拷贝的问题 // 因此解决深拷贝方式的不同实现了不同版本的智能指针 // 第一种智能指针 // C98 auto_ptr 原理 // RAII 具有指针类似的行为 资源转移来解决浅拷贝问题的 #include memory #if 0 class A { public: A() { cout A::A() endl; } ~A() { cout A::~A() endl; } int _a; int _b; }; void TestAutoPtr() { auto_ptrA ap1(new A()); ap1-_a 10; ap1-_b 20; // 当ap1拷贝构造ap2时ap1会将自己管理的资源 // 转移给ap2,然后ap1不在管理了即指向空 auto_ptrA ap2(ap1); ap2-_a 100; ap2-_b 200; // ap1-_a 10; // 运行崩溃因为ap1将资源转移走之后指向NULL auto_ptrA ap3; // 当ap2给ap3赋值时ap2会将自己管理的资源 // 转移给ap3然后ap2不在管理资源了即指向空 ap3 ap2; A* pa1 new A(); A* pa2 nullptr; pa2 pa1; pa1-_a 10; pa2-_b 20; } int main() { TestAutoPtr(); return 0; } #endif三、实现自己自己的auto_ptr#if 0 namespace bite { templateclass T class auto_ptr { public: // RAII auto_ptr(T* ptr nullptr) : _ptr(ptr) {} ~auto_ptr() { if (_ptr) { delete _ptr; _ptr nullptr; } } // 具有指针类似的行为 T operator*() { return *_ptr; } T* operator-() { return _ptr; } // 资源转移 auto_ptr(auto_ptrT ap) : _ptr(ap._ptr) { ap._ptr nullptr; 资源转移体现在对用来赋值的旧对象的指针置空 } auto_ptrT operator(auto_ptrT ap) { if (this ! ap) { if (_ptr) 新对象指针有内容的话需要先进行置空处理。 { delete _ptr; } _ptr ap._ptr; 利用旧对象指针给新对象指针赋值 ap._ptr nullptr; 同样是对旧对象的指针置空 } return *this; } private: T* _ptr; }; } #endif四、auto_ptr版本二加入资源权限的转移// C98中auto_ptr的改造 // RAII 具有指针类似的行为 解决浅拷贝的方式资管管理权限的转移 // 资管管理权限: 对资源释放的权限 namespace bite { templateclass T class auto_ptr { public: // RAII auto_ptr(T* ptr nullptr) : _ptr(ptr) , _owner(false) { if (_ptr) { _owner true; } } ~auto_ptr() { if (_ptr _owner) { delete _ptr; _ptr nullptr; _owner false; } } // 具有指针类似的行为 T operator*() { return *_ptr; } T* operator-() { return _ptr; } // 资源转移 auto_ptr(const auto_ptrT ap) : _ptr(ap._ptr) , _owner(ap._owner) { ap._owner false; } auto_ptrT operator(const auto_ptrT ap) { if (this ! ap) { if (_ptr _owner) { delete _ptr; } _ptr ap._ptr; _owner ap._owner; ap._owner false; } return *this; } private: T* _ptr; mutable bool _owner; // 如果为true表明该对象具有释放资源的权限 }; } class A { public: A() { cout A::A() endl; } ~A() { cout A::~A() endl; } int _a; int _b; }; void TestAutoPtr1() { bite::auto_ptrA ap1(new A()); ap1-_a 10; ap1-_b 20; // 当ap1拷贝构造ap2时ap1会将自己管理的资源 // 转移给ap2,然后ap1不在管理了即指向空 bite::auto_ptrA ap2(ap1); ap2-_a 100; ap2-_b 200; ap1-_a 10; // 运行成功 bite::auto_ptrA ap3; // 当ap2给ap3赋值时ap2会将自己管理的资源 // 转移给ap3然后ap2不在管理资源了即指向空 ap3 ap2; A* pa1 new A(); A* pa2 nullptr; pa2 pa1; pa1-_a 10; pa2-_b 20; delete pa1; } void TestAutoPtr2() { bite::auto_ptrA ap1(new A()); ap1-_a 10; ap1-_b 20; if (true) { bite::auto_ptrA ap2(ap1); ap1-_a 100; ap2-_b 200; } // 当if结束时ap2退出了自己的程序范围调用析构函数ap2将资源释放了 // 因此ap1内部的指针就变成了野指针 ap1-_a 1000; ap1-_b 2000; } // C标准建议在什么情况下都不要使用auto_ptr int main() { // TestAutoPtr1(); TestAutoPtr2(); return 0; }
C++——智能指针 auto_ptr
智能指针总结C 智能指针的本质是利用了栈对象在离开作用域时会自动调用析构函数的特性也就是RAII 机制来管理堆内存。这从根本上解决了传统裸指针忘记delete导致的内存泄漏以及异常发生时导致资源未释放的问题。标准的演进主要经历了四个代表性的指针第一std::auto_ptr它最大的问题在于“拷贝语义的控制权转移”。当你把一个auto_ptr赋值给另一个时原指针会被隐式置空。如果后续代码不小心继续使用了原指针就会导致段错误Crash。第二std::unique_ptr现代 C 的首选强调性能与所有权专属所有权。他要求同一时刻只能有一个unique_ptr指向该对象。它禁用了拷贝构造和拷贝赋值必须通过std::move()显式地进行所有权转移。更重要的是在默认情况下它的内存开销和裸指针一模一样属于零成本抽象。一般有俩种实现方式一种是c98里将其拷贝构造函数和赋值运算符重载只声明不定义并且设置为private权限 c11里将拷贝构造函数和赋值运算符重载函数后面跟上delete“尝试引用已经删除的函数”优先并且大量使用unique_ptr第三std::shared_ptr共享与协同强调引用计数与开销核心机制共享所有权。多个指针可以指向同一个对象内部通过引用计数Reference Counting来管理生命周期。当最后一个指向该对象的shared_ptr被销毁时引用计数清零内存才会被释放。底层细节高频考点它除了包含一个指向数据的指针还包含一个指向控制块Control Block的指针存放强/弱引用计数、自定义删除器等。最初使用一个int类型变量计数不能共享、后来使用static int 计数重新定义一个新对象导致计数清零再后来使用线程安全的指针类型变量计数memory库中就是这样实现的适合跨线程共享、生命周期不确定谁活到最后谁负责清理、多个独立组件需要引用同一个底层资源。第四std::weak_ptr幕后协助者强调解决死锁核心机制它是shared_ptr的小弟是一种不控制对象生命周期的“弱引用”。它指向一个由shared_ptr管理的对象但不增加强引用计数只增加弱引用计数。解决的痛点高频考点专门用来解决shared_ptr的循环引用Cyclic Reference问题。比如双向链表中节点互相持有shared_ptr会导致引用计数永远无法清零造成内存泄漏。将其中一个方向改为weak_ptr即可破局。接下来我给您画一下图详细说明一下这个情况一、RAII思想的引入#include iostream using namespace std; #if 0 // C中动态申请的资源需要用户自己手动释放 // 如果操作不当容易造成内存泄漏 // 能否做到让资源自动被释放RAII // RAII : 将资源交给对象管理对象被销毁时自动调用析构函数可以在析构函数中将资源释放掉 templateclass T class smart_ptr { public: smart_ptr(T* ptr nullptr) : _ptr(ptr) {} // 具有指针类似的效果 T operator*() { return *_ptr; } T* operator-() { return _ptr; } ~smart_ptr() { if (_ptr) { delete _ptr; _ptr nullptr; } } T* Get() { return _ptr; } private: T* _ptr; }; void TestSmartPtr1() { //int* p new int(10); //smart_ptrint sp(p); // ... //delete p; smart_ptrint sp(new int(10)); *sp 100; // ... } struct A { int a; int b; int c; }; void TestSmartPtr2() { smart_ptrint sp1(new int(10)); *sp1 100; smart_ptrA sp2(new A()); sp2-a 10; sp2-b 20; sp2-c 30; } // 上述smart_ptr已经很好了达到了刚开始的需求 // 新的问题类中涉及到资源的管理时如果没有显式实现拷贝构造 已经 赋值运算符重载 // 则编译器会以浅拷贝的方式实现smart_ptr就会有问题 // 解决能够深拷贝解决吗 答案不可以 int main() { // TestSmartPtr1(); TestSmartPtr2(); return 0; } #endif二、对于memory库中auto_ptr的使用// 智能指针对原生态指针进行了封装以类的方式管理用户的资源在析构方法中将资源释放掉 // RAII 具有指针类似的行为 如何解决浅拷贝的问题 // 因此解决深拷贝方式的不同实现了不同版本的智能指针 // 第一种智能指针 // C98 auto_ptr 原理 // RAII 具有指针类似的行为 资源转移来解决浅拷贝问题的 #include memory #if 0 class A { public: A() { cout A::A() endl; } ~A() { cout A::~A() endl; } int _a; int _b; }; void TestAutoPtr() { auto_ptrA ap1(new A()); ap1-_a 10; ap1-_b 20; // 当ap1拷贝构造ap2时ap1会将自己管理的资源 // 转移给ap2,然后ap1不在管理了即指向空 auto_ptrA ap2(ap1); ap2-_a 100; ap2-_b 200; // ap1-_a 10; // 运行崩溃因为ap1将资源转移走之后指向NULL auto_ptrA ap3; // 当ap2给ap3赋值时ap2会将自己管理的资源 // 转移给ap3然后ap2不在管理资源了即指向空 ap3 ap2; A* pa1 new A(); A* pa2 nullptr; pa2 pa1; pa1-_a 10; pa2-_b 20; } int main() { TestAutoPtr(); return 0; } #endif三、实现自己自己的auto_ptr#if 0 namespace bite { templateclass T class auto_ptr { public: // RAII auto_ptr(T* ptr nullptr) : _ptr(ptr) {} ~auto_ptr() { if (_ptr) { delete _ptr; _ptr nullptr; } } // 具有指针类似的行为 T operator*() { return *_ptr; } T* operator-() { return _ptr; } // 资源转移 auto_ptr(auto_ptrT ap) : _ptr(ap._ptr) { ap._ptr nullptr; 资源转移体现在对用来赋值的旧对象的指针置空 } auto_ptrT operator(auto_ptrT ap) { if (this ! ap) { if (_ptr) 新对象指针有内容的话需要先进行置空处理。 { delete _ptr; } _ptr ap._ptr; 利用旧对象指针给新对象指针赋值 ap._ptr nullptr; 同样是对旧对象的指针置空 } return *this; } private: T* _ptr; }; } #endif四、auto_ptr版本二加入资源权限的转移// C98中auto_ptr的改造 // RAII 具有指针类似的行为 解决浅拷贝的方式资管管理权限的转移 // 资管管理权限: 对资源释放的权限 namespace bite { templateclass T class auto_ptr { public: // RAII auto_ptr(T* ptr nullptr) : _ptr(ptr) , _owner(false) { if (_ptr) { _owner true; } } ~auto_ptr() { if (_ptr _owner) { delete _ptr; _ptr nullptr; _owner false; } } // 具有指针类似的行为 T operator*() { return *_ptr; } T* operator-() { return _ptr; } // 资源转移 auto_ptr(const auto_ptrT ap) : _ptr(ap._ptr) , _owner(ap._owner) { ap._owner false; } auto_ptrT operator(const auto_ptrT ap) { if (this ! ap) { if (_ptr _owner) { delete _ptr; } _ptr ap._ptr; _owner ap._owner; ap._owner false; } return *this; } private: T* _ptr; mutable bool _owner; // 如果为true表明该对象具有释放资源的权限 }; } class A { public: A() { cout A::A() endl; } ~A() { cout A::~A() endl; } int _a; int _b; }; void TestAutoPtr1() { bite::auto_ptrA ap1(new A()); ap1-_a 10; ap1-_b 20; // 当ap1拷贝构造ap2时ap1会将自己管理的资源 // 转移给ap2,然后ap1不在管理了即指向空 bite::auto_ptrA ap2(ap1); ap2-_a 100; ap2-_b 200; ap1-_a 10; // 运行成功 bite::auto_ptrA ap3; // 当ap2给ap3赋值时ap2会将自己管理的资源 // 转移给ap3然后ap2不在管理资源了即指向空 ap3 ap2; A* pa1 new A(); A* pa2 nullptr; pa2 pa1; pa1-_a 10; pa2-_b 20; delete pa1; } void TestAutoPtr2() { bite::auto_ptrA ap1(new A()); ap1-_a 10; ap1-_b 20; if (true) { bite::auto_ptrA ap2(ap1); ap1-_a 100; ap2-_b 200; } // 当if结束时ap2退出了自己的程序范围调用析构函数ap2将资源释放了 // 因此ap1内部的指针就变成了野指针 ap1-_a 1000; ap1-_b 2000; } // C标准建议在什么情况下都不要使用auto_ptr int main() { // TestAutoPtr1(); TestAutoPtr2(); return 0; }