兰州网站优化服务seo排名赚挂机
1.概念
在 C++ 中,智能指针是一种特殊的指针类型,它封装了裸指针(raw pointer)的行为,并通过 RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制自动管理动态分配的内存。智能指针的主要目的是简化内存管理,避免内存泄漏、悬空指针等问题。
2.执行机制
3.实现代码
实现过程就是前面类和对象的内容,只不过将其与异常处理杂糅起来了,具体细节见代码注释!
#include <iostream>
#include<functional>
using namespace std;
template <class 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()
{// 这里使用RAII的智能指针类管理new出来的数组以后,程序简单多了SmartPtr<int> sp1 = new int[10];SmartPtr<int> sp2 = new int[10];//SmartPtr<int> sp3(sp1); //通过sp1构造sp3 // 默认的拷贝构造函数和赋值运算符会进行浅拷贝,这会导致多个 SmartPtr 对象指向同一个资源。// 当这些对象被销毁时,多个析构函数会尝试释放同一个指针,导致未定义行为(通常是程序崩溃)。//所以记得把他注释掉,只是说可能怎么写,但是超级不建议for (size_t i = 0; i < 10; i++){sp1[i] = sp2[i] = i;}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;
}
对比一下运行结果:
这是调试时的界面,抛出的异常会被这个catch捕捉
4.智能指针的种类
C++ 标准库提供了几种常见的智能指针类型:
4.0 std::auto_ptr
4.1 std::unique_ptr
std::unique_ptr
是一种独占所有权的智能指针,表示它所指向的资源只能被一个 unique_ptr
对象拥有。当 unique_ptr
被销毁时,它会自动释放所管理的资源。
-
特点:
-
不可复制(copy),但可以移动(move)。
-
适合单所有权场景。
-
性能开销极小,几乎和裸指针相当。
-
4.2 std::shared_ptr
std::shared_ptr
是一种引用计数的智能指针,允许多个 shared_ptr
对象共享对同一资源的所有权。当最后一个 shared_ptr
被销毁时,资源会被自动释放。
-
特点:
-
支持复制(copy)。
-
支持移动(move)
-
适合共享所有权场景。
-
性能开销稍高,因为需要维护引用计数(底层实现)。
-
4.3 std::weak_ptr
std::weak_ptr
是一种弱引用指针,通常与 std::shared_ptr
一起使用。它观察一个资源,但不增加引用计数。weak_ptr
主要用于打破 shared_ptr
的循环引用问题。
-
特点:
-
不增加引用计数。
-
适合观察资源,但不希望影响资源的生命周期。
-
4.4 代码示例
我们先实现一个日期类
struct Date
{int _year;int _month;int _day;Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}~Date(){cout << "~Date()" << endl;}
};
4.4.1 auto_ptr
int main()
{//创建一个 auto_ptr<Date> 对象 ap1,它管理一个动态分配的 Date 对象auto_ptr<Date> ap1(new Date);// 拷贝时,管理权限转移,被拷贝对象ap1悬空//在 auto_ptr 的拷贝构造中,ap1 的所有权会被转移到 ap2,ap1 会变成一个悬空指针(即它不再管理任何资源)。auto_ptr<Date> ap2(ap1);//尝试通过 ap1 访问资源,但此时 ap1 已经是一个悬空指针,导致未定义行为(通常是程序崩溃或异常)ap1->_year++;return 0;
}
运行上述代码,触发报错:
4.4.2 unique_ptr
int main()
{unique_ptr<Date> up1 (new Date);// 不支持拷贝//unique_ptr<Date> up2(up1);// 支持移动,但是移动后up1也悬空,所以使用移动要谨慎unique_ptr<Date> up3(move(up1));return 0;
}
运行代码:触发报错
4.4.3 shared_ptr
int main()
{shared_ptr<Date> sp1(new Date);// 支持拷贝shared_ptr<Date> sp2(sp1);shared_ptr<Date> sp3(sp2);//use_count() 返回一个整数值,表示当前资源的引用次数。cout << sp1.use_count() << endl;sp1->_year++;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;return 0;
}
运行一下:
说明:
在 shared_ptr
的引用计数机制中,析构函数的调用次数与引用计数无关,只与资源是否被释放有关。当引用计数变为0时,资源被释放,析构函数被调用一次。因此,即使引用计数为3,析构函数也只会调用一次。
4.4.4 weak_ptr
这里我们要了解三个函数:
-
expired()
:检查资源是否已过期。 -
use_count()
:返回共享该资源的shared_ptr
对象的数量。 -
lock()
:获取一个shared_ptr
对象,如果资源未过期。
int main()
{shared_ptr<string> sp1(new string("11111111"));shared_ptr<string> sp2(sp1);// sp2与 sp1 共享同一个资源weak_ptr<string> wp = sp1; //wp指向 sp1 的资源cout << wp.expired() << endl; // 检查 wp 是否过期,输出 0(false),表示资源未过期cout << wp.use_count() << endl; // 输出 2,表示 sp1 和 sp2 共享资源// sp1和sp2都指向了其他资源,则weak_ptr就过期了sp1 = make_shared<string>("222222");cout << wp.expired() << endl;// 输出 1(true),表示资源已过期cout << wp.use_count() << endl;// 输出 0,表示没有 shared_ptr 共享资源sp2 = make_shared<string>("333333");cout << wp.expired() << endl; //同上cout << wp.use_count() << endl;wp = sp1;auto sp3 = wp.lock(); // 使用 wp.lock() 获取一个 shared_ptr 对象cout << wp.expired() << endl; // 输出 0(false),表示资源未过期cout << wp.use_count() << endl; // 输出 2,表示 sp1 和 sp3 共享资源*sp3 += "###";cout << *sp1 << endl;sp1 = sp2;return 0;
}
5. 手撕智能指针
由于我们要模拟引用计数,这里我们引入c++11的atomic,具体来说是atomic<int>
,是 C++11 中引入的一个模板类,用于支持原子操作。它确保对整数的操作是线程安全的,可以避免在多线程环境下出现数据竞争问题。 通常用于需要在多线程环境中进行原子操作的场景,比如计数器、标志位等。通过使用 atomic<int>
,可以确保对变量的读取和写入操作是原子的,从而避免竞态条件
namespace rens
{template<class T>class my_shared_ptr{public://在 C++ 中,explicit 是一个关键字,用于修饰构造函数,表示该构造函数只能通过直接初始化的方式调用,而不能通过隐式转换的方式调用。explicit 的主要目的是防止意外的隐式类型转换,从而提高代码的安全性和可读性。explicit my_shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new std::atomic<int>(1)){}// 定制删除器,面试时手撕时,不要写删除器部分template<class D>my_shared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new std::atomic<int>(1)), _del(del){}//当一个 my_shared_ptr 对象被赋值为另一个对象时,先释放当前资源,// 然后复制新对象的资源,并增加引用计数。my_shared_ptr(const my_shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}void release(){//如果引用计数为0,则释放资源。if (--(*_pcount) == 0){//delete _ptr;_del(_ptr);// 定制删除器delete _pcount;}}// sp1 = sp3my_shared_ptr<T>& operator=(const my_shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}~my_shared_ptr(){release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}private:T* _ptr;atomic<int>* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };};template<class T>class my_weak_ptr{public:my_weak_ptr(){}my_weak_ptr(const my_shared_ptr<T>& sp):_ptr(sp.get){}my_weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}private:T* _ptr = nullptr;};
}int main()
{rens::my_shared_ptr<Date> sp1(new Date);rens::my_shared_ptr<Date> sp2(sp1);rens::my_shared_ptr<Date> sp3(new Date);sp1 = sp1;sp1 = sp2;sp1 = sp3;sp2 = sp3;return 0;
}
补充:带删除器版本附加
template<class T>
void DeleteArrayFunc(T* ptr)
{delete[] ptr;
}
template<class T>
class DeleteArray
{
public:void operator()(T* ptr){delete[] ptr;}
};class Fclose
{
public:void operator()(FILE* ptr){cout << "fclose:" << ptr << endl;fclose(ptr);}
};
int main()
{/*std::shared_ptr<Date[]> sp1(new Date[10]);std::unique_ptr<Date[]> up1(new Date[10]);*/// 定制删除器 函数指针/仿函数/lambdarens::my_shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());rens::my_shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);auto delArrOBJ = [](Date* ptr) {delete[] ptr; };rens::my_shared_ptr<Date> sp4(new Date[5], delArrOBJ);// 实现其他资源管理的删除器rens::my_shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());rens::my_shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {cout << "fclose:" << ptr << endl;fclose(ptr);});rens::my_shared_ptr<Date> sp7(new Date);//unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);//unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);decltype对象的类型//unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);return 0;
}
6.内存泄漏
6.1 定义
内存泄漏是指程序在申请内存后,无法释放已申请的内存,导致可使用的内存越来越少。随着时间的推移,内存泄漏会导致系统性能下降,甚至可能导致程序崩溃或系统不稳定。
6.2 内存泄漏的危害
6.2.1 系统性能下降:
内存泄漏会导致可用内存逐渐减少,系统需要频繁地进行内存交换(swap),从而增加磁盘I/O操作,降低系统响应速度。随着时间的推移,系统可能会变得越来越慢,最终可能导致系统崩溃。
6.2.2 程序崩溃:
当内存泄漏严重时,系统可能无法为其他程序或进程分配足够的内存,导致程序崩溃或无法启动。在多线程环境中,内存泄漏可能导致线程无法正常运行,从而影响整个程序的稳定性。
6.2.3 资源浪费:
内存泄漏会导致系统资源的浪费,因为已分配的内存无法被其他程序或进程使用。长期的内存泄漏会导致系统资源的严重浪费,影响系统的整体性能。
6.2.4 安全性问题:
在某些情况下,内存泄漏可能导致敏感数据(如密码、密钥等)驻留在内存中,增加数据泄露的风险。内存泄漏可能导致系统不稳定,从而影响系统的安全性。
6.3 内存泄漏的常见原因
-
未释放的动态内存:在 C 和 C++ 中,使用
malloc
或new
分配的内存如果没有对应的free
或delete
,就会导致内存泄漏。 -
循环引用:在使用引用计数的智能指针(如
std::shared_ptr
)时,如果两个对象互相引用,可能会导致引用计数无法减到零,从而引发内存泄漏。 -
全局变量和静态变量:全局变量和静态变量在程序结束时才会被释放,如果程序运行时间较长,可能会导致内存泄漏。
-
异常处理不当:在异常处理中,如果未正确释放资源,可能会导致内存泄漏。
6.4 如何避免内存泄漏
-
使用智能指针:在 C++ 中,使用
std::unique_ptr
和std::shared_ptr
等智能指针可以自动管理内存,避免手动释放内存。 -
代码审查:定期进行代码审查,检查是否有未释放的内存。
-
使用内存检测工具:使用内存检测工具(如 Valgrind、LeakSanitizer 等)可以帮助发现内存泄漏问题。
-
避免循环引用:在使用引用计数的智能指针时,避免循环引用,可以使用
std::weak_ptr
来打破循环引用。 -
良好的编程习惯:
-
确保每个
malloc
或new
都有对应的free
或delete
。 -
在异常处理中,确保资源被正确释放。
-
到此,我们c++主线课程内容结束!后续可能会更新c++ 副线内容,下一阶段将主要是linux以及算法课博客的更新!