您的位置:首页 > 文旅 > 美景 > 宣传片拍摄清单_广州市又恢复48小时核酸检测_网络营销的特点不包括_seo 推广教程

宣传片拍摄清单_广州市又恢复48小时核酸检测_网络营销的特点不包括_seo 推广教程

2025/4/18 19:53:21 来源:https://blog.csdn.net/2401_83456040/article/details/147067881  浏览:    关键词:宣传片拍摄清单_广州市又恢复48小时核酸检测_网络营销的特点不包括_seo 推广教程
宣传片拍摄清单_广州市又恢复48小时核酸检测_网络营销的特点不包括_seo 推广教程

1. 智能指针的使用场景分析

通过下面的例子来分析:

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 a, b;cin >> a >> b;cout << Divide(a, b)<<endl;}catch(...){delete[]array1;delete[]array2;throw;}delete[]array1;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;
}

上面程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导致后⾯的delete没有得到执行,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本⾝也可能抛异常,连续的两个new和下面的Divide都可能会抛异常,让我们处理起来很麻烦。智能指针放到这样的场景里面就让问题变得简单。

2.RAII和智能指针的设计

RALL:

RAII是ResourceAcquisition Is Initialization的缩写,是⼀种管理资源的类的设计思想,本质是 ⼀种利用对象生命周期来管理获取到的动态资源,避免资源泄漏(这里的资源可以是内存、⽂件指针、网络连接、互斥锁等等)

 原理:

RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问, 资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。

 智能指针:

智能指针类除了满⾜RAII的设计思路,还要方便资源的访问,所以智能指针类还会像迭代器类⼀ 样,重载 operator*/operator->/operator[ ] 等运算符,方便访问资源。

 这里实现一个简单的智能指针:

template<class T>
class smartPtr
{
public:smartPtr(T* ptr):_ptr(ptr){}~smartPtr(){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()
{smartPtr<int> sp1 = new int[10];smartPtr<int> sp2 = new int[10];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;
}

这里抛出异常后,就不用担心,需要手动释放new出来的资源了,出了Func函数作用域,就会自动调用析构,帮助我们释放。

3. C++标准库智能指针的使用

C++标准库中的智能指针都在<memory>这个头文件下面, 智能指针有好几种,除了weak_ptr他们都符合RAII和像指针⼀样访问的行为,原理上而言主要是解决智能指针拷贝时的思路不同。

这里通过之前实现的Date类,来举例子:

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;}
};

3.1 auto_ptr

auto_ptr是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象,这是⼀个非常糟糕的设计,因为他会到被拷贝对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使用auto_ptr。

 例如:

int main()
{auto_ptr<Date> p1(new Date());auto_ptr<Date> p2 (p1);p1->_year++;//空指针解引用return 0;
}

这里运行时,就会报错,因为这里使用的是auto_ptr,资源被转移时,就会把p1架空,导致空指针的解引用。

3.2 unique_ptr

unique_ptr是C++11设计出来的智能指针,直译出来就是唯⼀指针,他的特点的不支持拷贝,只支持移动。如果不需要拷贝的场景就使用他。

 例如:

int main()
{unique_ptr<Date> up1(new Date);//unique_ptr<Date> up2(up1);不⽀持拷贝unique_ptr<Date> up3(move(up1));//up1->_day++;空指针解引用,虽然⽀持移动,但是移动后up1也悬空,所以使⽤移动要谨慎return 0;
}

3.3 shared_ptr

shared_ptr是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是支持拷贝, 也支持移动。如果需要拷贝的场景就需要使用他了。底层是用引用计数的方式实现的。

例如:

int main()
{shared_ptr<Date> sp1(new Date);// ⽀持拷⻉shared_ptr<Date> sp2(sp1);shared_ptr<Date> sp3(sp2);cout << sp1.use_count() << endl;sp1->_year=2025;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;return 0;
}

这里同样和unique_ptr有一样的问题,移动以后,原来的指针就会架空。

3.4 weak_ptr

weak_ptr是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上面的智能指针,他不⽀持RAII,也就意味着不能用它直接管理资源,weak_ptr的产生本质是要解决shared_ptr 的⼀个循环引用导致内存泄漏的问题。("循环引用"后面有详细介绍)

3.5 delete释放资源(删除器)

智能指针析构时默认是进⾏delete释放资源,这也就意味着如果不是new出来的资源,交给智能指 针管理,析构时就会崩溃。智能指针支持在构造时给⼀个删除器,删除器本质就是⼀个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器, 在智能指针析构时就会调用删除器去释放资源。

 例如:

unique_ptr<Date> up1(new Date[10]);
shared_ptr<Date> sp1(new Date[10]);

这样释放的时候就会崩溃。但是由于new[]经常使用,所以为了简洁⼀点, unique_ptr和shared_ptr都特化了⼀份[ ]的版本

这是库里面提供解决办法1:

unique_ptr<Date[]> up1(new Date[5]);
shared_ptr<Date[]> sp1(new Date[5]);

解决办法2,可以通过实现仿函数,lambda,或者函数指针来实现删除器,定制删除方式:

仿函数:

template<class T>
class Delarray
{
public:void operator()(T* ptr){delete[] ptr;}
};int main()
{unique_ptr<Date, Delarray<Date>> up1(new Date[5]);shared_ptr<Date> up2(new Date[5],Delarray<Date>());return 0;
}

补充说明:

unique_ptr 和 shared_ptr ⽀持删除器的⽅式有所不同:unique_ptr 是在类模板参数支持的, shared_ptr 是构造函数参数支持的

函数指针:

template<class T>
void DeleteArrayFunc(T* ptr)
{delete[] ptr;
}int main()
{unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);return 0;
}

 lambda表达式:

int main()
{auto delArrOBJ = [](Date* ptr) {delete[] ptr; };unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);shared_ptr<Date> sp4(new Date[5], delArrOBJ);return 0;
}

另外还可以定制实现一些其他资源管理的删除器

例如文件关闭:

class Fclose
{
public:void operator()(FILE* ptr){fclose(ptr);}
};int main()
{shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr);return 0;
}

补充:

a)shared_ptr 除了支持用指向资源的指针构造,还可以make_shared用初始化资源对象的值直接构造。

例如:

shared_ptr<Date> sp4=make_shared<Date>(2025,4,3);

b)shared_ptr 和 make_shared 都支持了operator bool的类型转换,如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

c)shared_ptr 和 unique_ptr 都得构造函数都使用explicit修饰,防止普通指针隐式类型转换成智能指针对象。

4. 智能指针的原理

首先模拟实现一下auto_ptr,unique_ptr这两个智能指针的实现比较简单,但还是由于auto_ptr的思路是拷贝时转移资源管理权给被拷贝对象,这种思路是不被认可的,也不建议使用。unique_ptr的思路是不⽀持拷贝。

auto_ptr:

template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){sp._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != ap){if (_ptr)delete _ptr;_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}~auto_ptr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

unique_ptr:

	template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;unique_ptr(const unique_ptr<T>&& up):_ptr(up._ptr){up.ptr = nullptr;}unique_ptr<T>& operator=(const unique_ptr<T>&& up){delete _ptr;_ptr = up._ptr;up._ptr = nullptr;}~unique_ptr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};

这里需要通过在拷贝构造函数,赋值运算符重载后面加delete修饰,直接不支持该函数。

shared_ptr:

shared_ptr的设计是引用计数,主要这就一份资源需要⼀个引用计数,但是引用计数用静态成员的方式是⽆法实现的,要使用堆上动态开辟的方式,构造智能指针对象时来⼀份资源,就要new⼀个引用计数出来。多个shared_ptr指向资源时就++引用计数,shared_ptr对象析构时就--引用计数,引用计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象,直接析构资源就可以。

	template<class T>class shared_ptr{public:explicit shared_ptr(T *ptr=nullptr):_ptr(ptr),_n(new int(1)){}template<class D>shared_ptr(T* ptr,D del):_ptr(ptr),_n(new int(1)),_del(del){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _n(sp._n), _del(sp._del){++(*_n);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_n = sp._n;++(*_n);_del = sp._del;}return *this;}void release(){if (--(*_n) == 0){//delete _ptr;_del(_ptr);delete _n;_n = nullptr;_ptr = nullptr;}}~shared_ptr(){release();}T* get() const{return _ptr;}int use_count() const{return *_n;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:int* _n;T* _ptr;function <void(T*)> _del = [](T* ptr) {delete ptr; };};

这里还加入了,一个删除器_del用于当new一个Date[5]这种空间时,就需要传一个删除器,来删除,如果还是使用delete来删除一片连续的空间,就会崩溃。其次就是_n这个变量,要等于0时,再去释放所有资源。

5. shared_ptr和weak_ptr

5.1 shared_ptr循环引用问题

下面给出一个例子:

struct ListNode
{int _data;std::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};int main()
{std::shared_ptr<ListNode> n1(new ListNode);std::shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

结果:

可以看到这里程序结束,并没有调用对应的析构,说明可能存在内存泄漏。

经过分析:左边的节点为n1,右边的为n2

a)右边的节点什么时候释放呢,左边节点中的_next管着,_next析构后,右边的节点就释放了。

b)_next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。

c)左边节点什么时候释放呢,左边节点由右边节点中的_prev管着,_prev析构后,左边的节点就释 放了。

d)_prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。

 逻辑上成功形成循环引用,谁都不会释放就形成了循环引用,导致内存泄漏。(怎么样解决呢?就需要用到下面讲的weak_ptr)

5.2 weak_ptr

weak_ptr不⽀持RAII,也不⽀持访问资源,weak_ptr构造时不支持绑定到资源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引数计数,那么就可以 解决上述的循环引用问题。

 (注意:weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的 shared_ptr已经释放了资源,那么他去访问资源就是很危险的。)

weak_ptr⽀持expired检查指向的资源是否过期,weak_ptr想访问资源时,可以调用lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

例如:

int main()
{std::shared_ptr<string> sp1(new string("111111"));std::shared_ptr<string> sp2(sp1);std::weak_ptr<string> wp = sp1;cout << wp.expired() << endl;cout << wp.use_count() << endl;// sp1和sp2都指向了其他资源,则weak_ptr就过期了sp1 = make_shared<string>("222222");cout << wp.expired() << endl;cout << wp.use_count() << endl;sp2 = make_shared<string>("333333");cout << wp.expired() << endl;cout << wp.use_count() << endl;return 0;
}

结果:

所以刚开始的循环引用问题如何解决?

就是利用weak_ptr解决:

struct ListNode
{int _data;std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};int main()
{std::shared_ptr<ListNode> n1(new ListNode);std::shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

结果:

这⾥改成 weak_ptr ,当 n1->_next = n2; 绑定 shared_ptr 时就不会增加 n2 的引用计数,不参与资源释放的管理,就不会形成循环引用。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com