IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《Effective C++》总结篇(资源管理) -> 正文阅读

[C++知识库]《Effective C++》总结篇(资源管理)

所谓资源就是你用了它就一定要还回去,不然会产生很多问题。
全篇围绕着RAII(Resource Acquisition Is Initialiaztion)模式结合智能指针进行讲述如何管理资源

条款十三:以对象管理资源

我们创建了对象之后,很可能会忘记delete它,或者因为某些原因无法delete它,最后导致内存泄漏。为了确保资源总是会被释放的,我们需要将对象资源放进对象内,当控制流离开,对象的析构函数会自动释放掉这些资源。
故依赖C++的析构函数自动调用机制,确保资源被释放。
智能指针auto_ptr正是利用析构函数自动对其所指对象调用delete。
关于智能指针可以看我这篇:c++ 四种智能指针介绍以及均手撸代码实现

以对象管理资源的关键思想就是:
1.获得资源后立刻放进管理对象内:初始化的时候就取得资源。(Resource Acquisition Is Initialiaztion RAII)
2.管理对象运用析构函数确保资源被释放:不论控制流如何离开区块,一旦对象被销毁其析构函数自然会调用,资源就能释放。

请记住:
为防止资源泄露。请使用RAII对象,它们在构造函数中初始化,在析构函数中释放资源。
两个常被使用的RAII classes分别是auto_ptr和shared_ptr。后者是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它指向null。

条款十四:在资源管理类中小心copying行为

有些时候auto_ptr或者shared_ptr可能不适合作为资源管理了,我们可能需要建立自己的资源管理类。
假设我们有这样一个RAII的类A。
类内部存了一个mutex*资源,在初始化的给mutex上锁,析构的时候解锁。
但是如果在他析构之前发生了copying行为会怎么样?

#include <iostream>
#include<mutex>
using namespace std;
class A
{
public:
	A(mutex* pm) :mutexPtr(pm) { mutexPtr->lock(); }//获得资源
	~A() { mutexPtr->unlock(); }//释放资源
private:
	mutex* mutexPtr;
};


int main()
{
	mutex m;
	if(1)
	{
		A lock(&m);//锁定m
		A lock1(lock);//将lock复制到lock1上会发送什么?==》 unlock of unowned mutex
	}
	return 0;
}

寄了,直接报错。
那么怎么解决?
目前提供以下解法:
1.禁止复制。只要禁止复制了就不会产生这个问题了,在根源上断绝问题发生。
2.对底层资源进行引用计数,使用shared_ptr封装一下即可。但有个问题,shared_ptr的缺省行为是”当引用计数为0则删除其所指物”,但我们只是希望metex解锁而已。这个问题的解决方案即是指定”删除器“(deleter),这是一个函数或者函数对象,当引用次数为0的时候会调用它。

#include <iostream>
#include<mutex>
using namespace std;
void unlock(mutex* x)
{
	std::cout << "Deleter function called" << std::endl;
	x->unlock();
}
class A
{
public:
	A(mutex* pm) :mutexPtr(pm, unlock)
	{
		mutexPtr.get()->lock();//获得资源
	}
private:
	shared_ptr<mutex> mutexPtr;
	
};


int main()
{
	mutex m;
	if(1)
	{
		A lock(&m);//锁定m
		A lock1(lock);//将lock复制到lock1上会发送什么?==》 Deleter function called
	}
	return 0;
}
/*
运行结果:
Deleter function called
*/

3.复制底层资源。通过深拷贝来复制对象,这样就能对一份资源(这里是mutex*)进行任意次数的复制

4.转移底部资源拥有权。只需要把锁转移到另一个对象即可。

请记住:
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
普遍而常见的RAII class copying行为是:抑制copying,施加引用计数法。不过其他行为也都可能被实现。

条款十五:在资源管理类中提供对原始资源的访问

假如你希望使用资源管理类中管理的资源,那么资源管理类最好能提供对原始资源的访问,比如shared_ptr/auto_ptr中的get()函数返回的就是资源的显示转换的原始指针。当然(->和*操作符)也能允许它们隐式转换成底部原始指针。

关于显示转换和隐式转换的区别:
显示转换就是强转,隐式转换是自动转换
显示转换需要写一个.get(),隐式转换则是直接使用即可

#include <iostream>
using namespace std;
class Resource
{

};


class ResourceManger
{
public:
	ResourceManger(Resource resource) :m_resource(resource) {}
	Resource get() const { return m_resource; }//显示转换
	operator Resource() const { return m_resource; }//隐式转换
private:
	Resource m_resource;
};

void UseResource(Resource resource)
{
	cout << "use Resource" << endl;
}
int main()
{
	Resource resource;
	ResourceManger rm(resource);
	UseResource(rm.get());//显示转换
	UseResource(rm);//隐式转换
	return 0;
}
/*
运行结果:
use Resource
use Resource
*/

但这样隐式转换会有安全问题
因为你可以这样写:Resource rm1 = rm;
哇!本来应该是只能这样ResourceManger rm1 = rm;写的,但是你可以隐式转换,所以可以让rm隐式转换成Resource,然后才复制给rm1。这种的话rm1和rm共享了一份资源,当rm销毁时,rm1里面字体也会销毁!

所以我们应该让接口容易使用,不易被误用。所以还是最好不要用隐式转换了!

请记住:
APIs往往要求访问原始资源,所以每一个RAII class应该提供一个”取得其所管理的资源”的办法
对原始资源的访问可能经由显示转换或隐式转换。一般而已显示转换比较安全,隐式转换对客户方便。

条款十六:成对使用new和delete时要采取相同的形式

这一条款很简单,我随便简单提一下即可。
new要搭配delete
new [n] 要搭配 delete []
如果不一一对应使用会发生未定义事件。

请记住:如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]

条款十七:以独立语句将newed对象置于智能指针

此条款也简单,我也简单提一下。
我们看这一段代码

#include <iostream>
using namespace std;
class A
{
public:
	A() { cout << "A" << endl; }
};
void Process(shared_ptr<A> a, int priority)
{

}
int priority() { cout << "priority" << endl; return 1; }
int main()
{
	Process(shared_ptr<A>(new A()), priority());
	return 0;
}

15行这一句Process(shared_ptr<A> (new A()), priority());
执行了三个事情:
1.调用priority
2.执行new A
3.调用shared_ptr的构造函数

由于编译器的不同,我们是无法知道他们的调用次序的,假设是按照这样的顺序
new A -》 调用pirority -》调用shared_ptr的构造函数
那么如果在priority发生异常的话!!!那么A对象就不会被shared_ptr对象管理!
很有可能就会发生内存泄漏。
解决方案就是把new A和调用shared_ptr的构造函数放一起,让他们绑定在一起同时执行。
调用pirority则单独出来。

shared_ptr<A> a = shared_ptr<A>(new A());
Process(a, priority());

请记住:
以独立语句将newed对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-21 18:44:48  更:2022-05-21 18:47:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 22:06:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码