网站备案期间如何,建设商务网站的费用,在internet上建设网站,网站开发的公司属于什么行业1. 什么是智能指针
1.1 RALL设计思想 RAII#xff08;Resource Acquisition Is Initialization#xff0c;资源获取即初始化#xff09;是一种资源管理类的设计思想#xff0c;广泛应用于C等支持对象导向编程的语言中。它的核心思想是将资源的管理与对象的生命周期紧密绑定…1. 什么是智能指针
1.1 RALL设计思想 RAIIResource Acquisition Is Initialization资源获取即初始化是一种资源管理类的设计思想广泛应用于C等支持对象导向编程的语言中。它的核心思想是将资源的管理与对象的生命周期紧密绑定通过在对象的构造函数中获取资源并在析构函数中释放资源以此来确保资源总是被正确管理避免资源泄露等问题。RAII利用了C中对象自动调用析构函数的特性即使在发生异常的情况下也能保证资源被适时释放 1.2 智能指针的概念
智能指针是C标准库中的一种高级指针管理工具它们通过自动管理动态分配的内存来帮助预防内存泄漏和其他资源管理错误。
简单来说智能指针是包装了指针的类当智能指针对象的生命周期结束时其析构函数会对指针指向的动态资源进行释放。
也就是说我们将动态申请的内存交给智能指针对象来管理将其的生命周期与智能指针的生命周期进行绑定以此实现对动态分配的内存的自动释放。
智能指针相当于是给普通的指针加上了一个自动释放资源的功能在行为上模拟普通指针的行为重载了各种指针相关的操作符(operator*、operator-、operator[]等)因此叫做智能指针。
1.3 智能指针的应用场景
在C中智能指针的引入是为了简化和自动化内存管理特别是在处理动态分配内存时智能指针能够帮助防止常见的内存错误如内存泄漏、悬挂指针和双重删除等。
在C11之前程序员需要手动管理动态分配的内存这要求他们在适当的时机使用new和delete操作符来分配和释放内存。
这种管理方式不仅容易出错而且在遇到异常时尤其难以保证资源被正确清理。
下面程序中我们可以看到假如异常被抛出就会导致array1和array2未被正常释放。
我们要处理这种情况可以考虑先将异常捕获并将资源释放之后重新抛出但这样的做法可维护性差每此申请动态资源都需要再catch块中新增释放操作。除此之外假如程序的其他部分抛出了异常(例如new也可能抛出异常)catch块中的释放操作也不会被执行。
double Divide(int a, int b)
{// 当b 0时抛出异常if (b 0){throw Divide by zero condition!;} else{return (double)a / (double)b;}
}void Func()
{int* array1 new int[10];int* array2 new int[10];try{int len, time;cin len time;cout Divide(len, time) endl;} catch(...){cout delete [] array1 endl;cout delete [] array2 endl;delete[] array1;delete[] array2;throw; // 异常重新抛出捕获到什么抛出什么} // ...cout delete [] array1 endl;delete[] array1;cout delete [] array2 endl;delete[] array2;
} int main()
{try{Func();} catch(const char* errmsg){cout errmsg endl;} catch(const exception e){cout e.what() endl;} catch(...){cout 未知异常 endl;} return 0;
}
当引入智能指针之后操作起来就要简单得多了
templateclass T
class SmartPtr
{
public:// RAIISmartPtr(T* ptr): _ptr(ptr){}~SmartPtr(){cout delete[] _ptr endl;delete[] _ptr;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}T operator[](size_t i){return _ptr[i];}
private:T* _ptr;
};double Divide(int a, int b)
{// 当b 0时抛出异常if (b 0){throw Divide by zero condition!;} else{return (double)a / (double)b;}
}void Func()
{SmartPtrint array1(new int[10]);SmartPtrint array2(new int[10]);int len, time;cin len time;cout Divide(len, time) endl;
}int main()
{try{Func();} catch(const char* errmsg){cout errmsg endl;} catch(const exception e){cout e.what() endl;} catch(...){cout 未知异常 endl;} return 0;
} 2. C标准库中的智能指针
智能指针在设计时存在一个较大的难题那就是拷贝时的行为如何设计
假如我们直接将一个智能指针内部的指针拷贝给另一个智能指针那么当两个智能指针的生命周期都结束时同一个动态资源就会被释放两次。
针对这个问题C标准库中给出了几种不同的方案这些智能指针都在memory这个头文件下面我们包含memory就可以使用了。
其中weak_ptr比较特殊它仅用于辅助shared_ptr处理循环引用的问题我们在第3部分再解释。
2.1 std::auto_ptr
auto_ptr是C98时设计出来的智能指针他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象这是一个非常糟糕的设计因为他会到被拷贝对象悬空访问报错的问题C11设计出新的智能指针后强烈建议不要使用auto_ptr。
C11出来之前很多公司也是明令禁止使用这个智能指针的。
个人感觉auto_ptr是最贴合设计初衷的名字应该给下面行为最接近普通指针shared_ptr使用但可惜好名字被狗用了。
实现示例
namespace lbz
{templateclass Tclass auto_ptr{public:auto_ptr(T* ptr): _ptr(ptr){}auto_ptr(auto_ptrT sp):_ptr(sp._ptr){// 管理权转移sp._ptr nullptr;}auto_ptrT operator(auto_ptrT ap){// 检测是否为⾃⼰给自己赋值if (this ! ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr ap._ptr;ap._ptr NULL;}return*this;}~auto_ptr(){if (_ptr){cout delete: _ptr endl;delete _ptr;}} // 像指针一样使用T operator*(){return *_ptr;} T* operator-(){return _ptr;}private:T* _ptr;};
}
2.2 std::unique_ptr
std::unique_ptr是一种独占所有权的智能指针它确保在任何时刻只有一个unique_ptr实例可以拥有和管理所指向的对象。一旦unique_ptr实例超出其作用域或被显式重置它所管理的对象将被自动释放。
unique_ptr不支持拷贝操作但可以通过移动语义来转让所有权。
实现示例
namespace lbz
{templateclass Tclass unique_ptr{public:explicit unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout delete: _ptr endl;delete _ptr;}}// 像指针⼀样使用T operator*(){return *_ptr;} T* operator-(){return _ptr;} // 禁止直接拷贝unique_ptr(const unique_ptrT sp) delete;unique_ptrT operator(const unique_ptrT sp) delete;unique_ptr(unique_ptrT sp):_ptr(sp._ptr){sp._ptr nullptr;} unique_ptrT operator(unique_ptrT sp){delete _ptr;_ptr sp._ptr;sp._ptr nullptr;}private:T* _ptr;};
}
2.3 std::shared_ptr
std::shared_ptr提供了共享所有权的智能指针允许多个shared_ptr实例共享同一个对象的所有权。对象的生命周期由最后一个持有该对象的shared_ptr实例决定。当没有任何shared_ptr实例指向该对象时对象会被自动释放。
shared_ptr是行为最贴近普通指针的智能指针实践当中的使用最多所以一般提到智能指针默认就是指shared_ptr。
shared_ptr的原理
shared_ptr内部使用引用计数来跟踪当前有多少个shared_ptr实例正在引用同一个对象。
为了实现这一点我们引入了一个动态申请的int对象来进行引用计数(即下面代码中的_pcount)。
当直接使用指针进行构造时申请一个int对象为其计数当进行拷贝构造时int对象。
注意这里不能使用静态成员变量来进行计数因为静态成员变量统计的是shared_ptr的数量而不是指向某一个资源的shared_ptr的数量。
删除器
智能指针析构时默认是进行delete释放资源这也就意味着如果不是new出来的资源交给智能指针管理析构时就会崩溃。
智能指针支持在构造时给一个删除器所谓删除器本质就是一个可调用对象这个可调用对象中实现你想要的释放资源的方式当构造智能指针时给了定制的删除器在智能指针析构时就会调用删除器去释放资源。 注意unique_ptr的删除器通过在实例化模板时显式给出类型进行传递shared_ptr只需要在构造函数的参数列表中给出可调用对象即可(lambda表达式使用更加方便)。 因为new[]经常使用所以为了使用方便unique_ptr和shared_ptr都特化了一份[]的版本
unique_ptrDate[] up1(new Date[5]);
shared_ptrDate[] sp1(new Date[5]);
在释放资源时会用delete[]来进行释放。
其他注意事项 1. shared_ptr 除了支持用指向资源的指针构造还支持 make_shared 用初始化资源对象的值直接构造 template class T, class... Args
shared_ptrT make_shared(Args... args); 2. shared_ptr 和 unique_ptr 都支持了operator bool的类型转换如果智能指针对象是一个空对象没有管理资源则返回false否则返回true意味着我们可以直接把智能指针对象给if判断是否为空。 3. shared_ptr 和 unique_ptr 的构造函数都使用了explicit 修饰以防止普通指针隐式类型转换成智能指针对象。 实现示例 需要注意的是我们这里实现的shared_ptr和weak_ptr都是以最简洁的方式实现的只能满足基本的功能这里的weak_ptr.lock等功能是无法实现的想要实现就要把shared_ptr和weak_ptr一起改了把引用计数拿出来放到一个单独类型shared_ptr和weak_ptr都要存储指向这个类的对象才能实现有兴趣可以去翻翻源代码。 namespace lbz
{templateclass Tclass shared_ptr{public:shared_ptr(T* ptr, functionvoid(T*) del ([](T* ptr) {delete ptr; })):_ptr(ptr), _pcount(new int(1)), _del(del){}shared_ptr(const shared_ptrT sp):_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del){(*_pcount);}void release(){if (--(*_pcount) 0){delete _pcount;_del(_ptr);_pcount nullptr;_ptr nullptr;}}~shared_ptr(){release();}shared_ptrT operator(const shared_ptrT sp){// 只剩一个且自己给自己赋值时必须这样解决if (_ptr ! sp._ptr){release();_ptr sp._ptr;_pcount sp._pcount;_del sp._del;(*_pcount);}return *this;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}T operator[](size_t i){return _ptr[i];}T* get() const{return _ptr;}int use_count() const{return *_pcount;}private:T* _ptr;int* _pcount;functionvoid(T*) _del;};
} 3. 循环引用
假如我们在双向链表中使用shared_ptr来管理资源会发生什么
#includememory
using namespace std;struct ListNode
{ListNode(int val):_val(val){}int _val;std::shared_ptrListNode _prev nullptr;std::shared_ptrListNode _next nullptr;
};int main()
{std::shared_ptrListNode n1(new ListNode(10));std::shared_ptrListNode n2(new ListNode(20));n1-_next n2;n2-_prev n1;return 0;
} n1结点不仅被n1所指向也被n2结点的prev所指向引用计数为2n2同理。
当程序结束时n1、n2的生命周期结束对应结点的引用计数各自减一但此时两个结点仍各有1的引用计数不会被正常释放。
只有当n2被释放n2-prev的生命周期结束n1才会被释放但n2要被释放就需要n1先被释放使n1-next的生命周期结束。
这样就导致两个结点互相引用、相互依存始终不会被释放。
为了解决这个问题就引入了weak_ptr来辅助shared_ptr的使用weak_ptr相比于shared_ptr其不会增加被指向资源的引用计数也不负责资源的释放将prev和next替换为weak_ptr就可以避免循环引用。
std::weak_ptr
std::weak_ptr是一种辅助智能指针它与shared_ptr一起使用用于避免循环引用问题。
相比于shared_ptrweak_ptr不增加对象的引用计数因此它不会延长对象的生命周期。
weak_ptr不支持RAII也不支持访问资源所以我们看文档发现weak_ptr构造时不支持绑定到资源只支持绑定到shared_ptr绑定到shared_ptr时不增加shared_ptr的引用计数那么就可以解决上述的循环引用问题。
weak_ptr也没有重载operator* 和operator-等因为他不参与资源管理那么如果他绑定的shared_ptr已经释放了资源那么他去访问资源就是很危险的。weak_ptr支持expired检查指向的资源是否过期use_count也可获取shared_ptr的引用计数weak_ptr想访问资源时可以调用lock返回一个管理资源的shared_ptr如果资源已经被释放返回的shared_ptr是一个空对象如果资源没有释放则通过返回的shared_ptr访问资源是安全的。
namespace lbz
{templateclass Tclass weak_ptr{public:weak_ptr(){}weak_ptr(const shared_ptrT sp):_ptr(sp.get()){}weak_ptrT operator(const shared_ptrT sp){_ptr sp.get();return *this;}private:T * _ptr nullptr;};
}
4. 智能指针使用建议 优先使用unique_ptr来管理单个对象的所有权因为它通常比shared_ptr更轻量级且更不容易产生错误。使用shared_ptr来管理共享所有权的资源尤其是在多个对象或函数需要访问同一个资源的情况下。使用weak_ptr来避免与shared_ptr相关的循环引用问题。避免在全局作用域或静态存储持续期间使用智能指针因为这可能会导致不必要的复杂性和潜在的资源管理问题。在可能发生异常的代码路径中使用智能指针以确保即使在异常发生时资源也能被正确释放。当向容器或算法传递所有权时使用std::move来将unique_ptr转换为右值引用以便容器或算法可以接管所有权。 5. shared_ptr的线程安全问题
shared_ptr的引用计数对象在堆上如果多个shared_ptr对象在多个线程中进行shared_ptr的拷贝析构时会访问修改引用计数就会存在线程安全问题所以shared_ptr引用计数是需要加锁或者原子操作保证线程安全的。
shared_ptr指向的对象也是有线程安全的问题的但是这个对象的线程安全问题不归shared_ptr管它也管不了应该有外层使用shared_ptr的人进行线程安全的控制。
下面的程序会崩溃或者A资源没释放bit::shared_ptr引用计数从int*改成atomicint*就可以保证引用计数的线程安全问题或者使用互斥锁加锁也可以。
struct AA
{int _a1 0;int _a2 0;~AA(){cout ~AA() endl;}
};int main()
{bit::shared_ptrAA p(new AA);const size_t n 100000;mutex mtx;auto func [](){for (size_t i 0; i n; i){// 这里智能指针拷贝会计数bit::shared_ptrAA copy(p);{unique_lockmutex lk(mtx);copy-_a1;copy-_a2;}}};thread t1(func);thread t2(func);t1.join();t2.join();cout p-_a1 endl;cout p-_a2 endl;cout p.use_count() endl;return 0;
}
6. C11和boost中智能指针的关系
Boost库是为C语言标准库提供扩展的一些C程序库的总称Boost社区建立的初衷之一就是为C的标准化工作提供可供参考的实现Boost社区的发起人Dawes本人就是C标准委员会的成员之一。
在Boost库的开发中Boost社区也在这个方向上取得了丰硕的成果C11及之后的新语法和库有很多都是从Boost中来的。 • C 98 中产生了第一个智能指针auto_ptr • C boost给出了更实用的scoped_ptr / scoped_array和shared_ptr / shared_array和 weak_ptr等 • C TR1引入了shared_ptr等不过注意的是TR1并不是标准版 • C 11引入了unique_ptr和shared_ptr和weak_ptr。 需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
7. 内存泄露
7.1 内存泄露的定义和危害
内存泄露是指程序在运行过程中动态分配的内存未能被正确释放或回收导致这部分内存无法供其他程序或同一程序的其他部分使用。随着时间的推移未被释放的内存会被不断累积可能导致系统可用内存减少进而使程序运行变慢、响应时间增加甚至可能引发系统崩溃或资源耗尽。
7.2 如何检测内存泄露
检测内存泄露通常需要使用专门的工具这些工具可以在程序运行时分析内存的分配和释放情况。常见的内存泄露检测工具包括Valgrind、AddressSanitizer、Visual Studio的内存诊断工具等。这些工具能够帮助开发人员识别不被释放的内存分配并定位到代码中的具体位置。
7.3 如何避免内存泄露
避免内存泄露的关键在于遵循良好的编程习惯和系统设计原则 严格管理内存分配和释放确保每次动态内存分配后都有相应的释放操作。使用智能指针和内存池等技术来自动管理内存减少手动内存管理的错误。进行定期的代码审查以发现潜在的内存泄露风险点。编写单元测试确保内存管理的正确性。对于不熟悉的库或框架仔细阅读文档了解其内存管理的细节。