EC笔记:第三部分:13、以对象管理资源
C++相比Java等含有gc的语言来说,内存管理方面(也包括资源管理)比较令人头疼。一些初级程序员,甚至是一些经验丰富的老程序员,也会经常在资源管理上犯错。这时候就需要一个能够自动管理资源的东西(gc),但是由于C++本身没有提供,那么只有我们自己实现了。
本节我不打算直接按照《Effective C++》本节的内容进行写作,而是手动实现一个智能指针(想想还有些小激动呢^_^)。
首先,我们先编写一个测试代码(先写测试代码总是一个好习惯):
//test.h
#pragma
once
#include
<iostream>
using
namespace
std;
class
TestClass {
private:
int
a;
double
b;
public:
TestClass(int
t) {
a = b = t;
cout
<<
"TestClass(int)"
<<
endl;
}
TestClass(int
t,double
u) {
a = t;
b = u;
cout
<<
"TestClass(int,double)"
<<
endl;
}
TestClass() {
a = b = 0;
cout
<<
"TestClass() "
<<
endl;
}
~TestClass() {
cout
<<
"~TestClass()"
<<
endl;
}
void
output() {
cout
<<
"a="
<<
a
<<
",b="
<<
b
<<
endl;
}
};
我们用这个类作为测试用的类(在构造函数和析构函数设置输出语句,便于观察)
然后我们实现简单的对象内存管理:
//SFAutoPtr.h
#pragma
once
template<typename
T>
class
SFAutoPtr {
private:
T* pointer; //对象指针
size_t *ref_count; //引用计数
void
dec() { //减少引用计数
,则应该释放资源
delete
pointer;
pointer = nullptr;
delete
ref_count;
ref_count = nullptr;
return;
}
--*ref_count;
}
void
inc() { //增加引用计数
++*ref_count;
}
public:
SFAutoPtr() : //默认构造函数,生成一个指针
pointer(new
T),
ref_count(new
size_t(0)) {}
template<typename ... Init_Type>
SFAutoPtr(Init_Type...args) : //带参数的构造函数,对象带有指针
pointer(new
T(args...)),
ref_count(new
size_t(0)) {}
SFAutoPtr(const
SFAutoPtr<T>& other) { //拷贝构造函数,增加引用计数
pointer = other.pointer;
ref_count = other.ref_count;
inc();
}
bool
operator==(const
SFAutoPtr<T>& other) const{ //等于操作符,判断指针是否相等,这时候不应该比较ref_count
return
pointer == other.pointer;
}
const
SFAutoPtr<T>& operator=(const
SFAutoPtr<T>& other) { //赋值运算符,需要将当前引用计数-1,赋值的引用计数+1
if(this == &other)
return *this;
dec();
pointer = other.pointer;
ref_count = other.ref_count;
inc();
return *this;
}
T
operator*(int) { //解引用运算符
return *pointer;
}
operator
T*() { //指针运算符,适用于使用指针作为参数的函数
return
pointer;
}
T* operator->() { //成员访问操作符
return pointer;
}
~SFAutoPtr() { //析构函数,需要将引用计数-1
dec();
}
};
该类使用一个pointer存储原始指针,并开辟一个size_t的ref_count变量作为引用计数。值得注意的是,在一个指针的多个副本中,共用一份ref_count,这就保证了一个指针对应的引用计数都是相等的。
我们编写一些测试代码:
先进行最简单的测试,直接定义智能指针,是否会释放对象?
#include
"SFAutoPtr.h"
#include
"test.h"
int
main() {
SFAutoPtr<TestClass> p1;
SFAutoPtr<TestClass> p2(5);
SFAutoPtr<TestClass> p3(5,3.5);
}
运行结果如下:
TestClass()
TestClass(int)
TestClass(int,double)
~TestClass()
~TestClass()
~TestClass()
可以看到,管理的对象都正常释放。
再来一个稍微复杂的。
#include
"SFAutoPtr.h"
#include
"test.h"
int
main() {
SFAutoPtr<TestClass> p1;
SFAutoPtr<TestClass> p2(5);
SFAutoPtr<TestClass> p3(5,3.5);
p1->output();
p2->output();
p3->output();
p2
=
p1;
p1->output();
p2->output();
p3->output();
p1
=
p2;
p1->output();
p2->output();
p3->output();
p3
=
p2;
p1->output();
p2->output();
p3->output();
}
输出结果为:
TestClass()
TestClass(int)
TestClass(int,double)
a=0,b=0
a=5,b=5
a=5,b=3.5
~TestClass()
a=0,b=0
a=0,b=0
a=5,b=3.5
a=0,b=0
a=0,b=0
a=5,b=3.5
~TestClass()
a=0,b=0
a=0,b=0
a=0,b=0
~TestClass()
在这段代码中,p1首先给p2赋值,这时候,p1管理的内存区域引用计数+1,p2管理的内存区域引用计数-1,因为p2的引用计数本来为0,所以这个时候,p2的ref_count和point指向的内存被释放。
接下来,p2对p1赋值,因为现在p2==p1,所以赋值的结果就是不执行任何操作。
最后,p2给p3赋值,p3管理的内存被释放,p2的引用计数再次+1,(这个时候p1,p2,p3指向同一块内存,引用计数为3-1=2)。
然后p1,p2,p3的作用域结束,都发生析构操作,假设析构的顺序是p1,p2,p3,那么,当p1,p2析构后,p3的引用计数等于0(三个的引用计数共享),所以在p3发生析构时,会将内存释放掉。
尝试拷贝构造函数:
#include
"SFAutoPtr.h"
#include
"test.h"
int
main() {
SFAutoPtr<TestClass> p1;
SFAutoPtr<TestClass> p2(p1);
SFAutoPtr<TestClass> p3(p1);
}
输出结果为:
TestClass()
~TestClass()
这个不多说,因为p1,p2,p3指向相同内存区,所以只发生一次构造,一次析构。
当传递参数时会发生什么?
#include
"SFAutoPtr.h"
#include
"test.h"
void
func(TestClass *p) {
cout
<<
"call func"
<<
endl;
}
int
main() {
SFAutoPtr<TestClass> p1;
func(p1);
}
输出结果为:
TestClass()
call func
~TestClass()
至此,对智能指针的测试基本就结束了。
既然智能指针这么好用,那应该注意什么呢?
首先,不要将智能指针和普通指针混用。虽然智能指针提供了operator T* 接口,但是这只是为了兼容使用普通指针作为参数的函数。例如上面例子的func函数。
其次,不要给一个智能指针赋值一个普通指针。当然,如果你很好地遵循上面的那条规则,这条规则就是多余的。常见的智能指针的实现方式往往是用一个普通指针作为参数进行初始化,然而,这样会存在一个隐患,观察以下代码:
int main() {
int *p = new int;
{
AutoPtr<int*> t(p);
}
*p = 5;
}
AutoPtr的作用域结束后,会删除管理的指针p,但是:在初始化之前我们并不知道这个普通指针p有多少引用。所以,我们只能通过:
AutoPtr<int*> t(new int);
这样的语句来创建智能指针对象,确保初始化的时候引用计数为0。而我刚在实现的智能指针,强行把内存分配与初始化的动作均放在智能指针内部,确保初始化的时候引用计数为0。使代码更简洁,更安全。要么不用,要用就保证安全。事实上,我也没有提供通过普通指针来构造或赋值的接口,也是这个原因。
最后,不要尝试删除一个智能指针管理的内存。SFAutoPtr没有重载delete运算符,因为没有这个必要。但是可能会有人编写出下面这样的代码:
#include
"SFAutoPtr.h"
#include
"test.h"
int
main() {
SFAutoPtr<TestClass> p1;
delete
p1; //强烈禁止,SFAutoPtr已经为我们管理了内存
}
运行的时候就会出现崩溃。因为p1管理的内存区域会被释放两次。
既然使用了智能指针,就要充分相信智能指针可以做好内存管理。
我这里实现的智能指针只是一个简化版本,实际的智能指针要复杂得多(包括多各种异常的处理),有兴趣的可以尝试一下。
EC笔记:第三部分:13、以对象管理资源的更多相关文章
- 《Effective C++》学习笔记条款13 以对象管理资源
条款 13 :以对象管理资源 例: voidf() { Investment *pInv = createInvestment(); ... ...
- Effective C++(13) 用对象管理资源
问题聚焦: 从这条准则开始,都是关于资源管理的. 资源,一旦用了它,将来必须还给系统. 本条准则,基于对象的资源管理办法,建立在C++的构造函数,析构函数和拷贝函数(拷贝构造函数和重载赋值操作符)的基 ...
- 读书笔记 effective c++ Item 13 用对象来管理资源
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...
- Effective C++ 条款13/14 以对象管理资源 || 在资源管理类中小心拷贝行为
三.资源管理 资源就是一旦你使用了它,将来不用的时候必须归还系统.C++中最常用的资源就是动态内存分配.其实,资源还有 文件描述符.互斥器.图形界面中的字形.画刷.数据库连接.socket ...
- effective C++ 读书笔记 条款14 以对象管理资源
如果我们使用一个投资行为的程序库: #include "stdafx.h" #include <iostream> #include <memory> us ...
- [Effective C++ --013]以对象管理资源
这一节基本讲述的是将资源放进管理对象,防止忘记释放资源. 1.一般New和Delete使用场景 void fun() { SimpleClass* pSimpleClass1 = new Simple ...
- 以对象管理资源——C++智能指针auto_ptr简介
auto_ptr是C++标准库提供的类模板,它可以帮助程序员自动管理用new表达式动态分配的单个对象.auto_ptr对象被初始化为指向由new表达式创建的对象,当auto_ptr对象的生命期结束时, ...
- Effective C++ ----以对象管理资源
以对象管理资源 通过对象的析构函数的自动调用来自动释放资源 第一部分:几种典型的以对象管理资源的例子 1. STL::auto_ptr 获取资源后立刻放入资源管理对象 std::auto_ptr< ...
- 《JavaScript权威指南》学习笔记 第三天 找个对象
现实生活中真的对象没有找到,在JavaScript 里左一个对象又一个对象,搞的我也是晕晕乎乎不知所云.人事复杂,人心难懂.我虽然是文科生,但是也不善于巧言.还是在js里面找找对象吧.那么我们今天就从 ...
随机推荐
- Mac OS X上IntelliJ IDEA 13与Tomcat 8的Java Web开发环境搭建
这标题实在有点拗口,不知道怎么写好,但看了标题也就明白文本的内容.最近几天在折腾这些玩意儿,所以写写总结.除了环境搭建,本文还是一篇入门级的上手教程. 去下载一些东西 JDK安装 Tomcat安装 T ...
- 《HiWind企业快速开发框架实战》(0)目录及框架简介
<HiWind企业快速开发框架实战>(0)目录及框架简介 本系列主要介绍一款企业管理系统快速开发框架,该框架旨在快速完成企业管理系统,并实现易维护可移植的目标. 使用逐个系统模块进行编码的 ...
- .NET实现微博粉丝服务平台接口
[文章摘要]Senparc.Weixin.MP虽然是微信公众号的SDK,但由于易信公众号和新浪微博粉丝服务平台也提供了微信兼容接口,所以也可以使用其快速实现相应的服务,当然微博由于与微信存在差异,如果 ...
- MySQL KEY分区
200 ? "200px" : this.width)!important;} --> 介绍 KEY分区和HASH分区相似,但是KEY分区支持除text和BLOB之外的所有数 ...
- 如何理解T-SQL中Merge语句
写在前面的话:之前看过Merge语句,感觉没什么用,完全可以用其他的方式来替代,最近又看了看Merge语句,确实挺好用,可以少写很多代码,看起来也很紧凑,当然也有别的优点. ====正文开始===== ...
- Android开发学习之路-Android N新特性-多窗口模式
我们都知道,在最新的Android N系统中,加入了一个新的功能,就是多窗口模式.多窗口模式允许我们在屏幕上显示两个窗口,每个窗口显示的内容不同,也就是说,我们可以一遍看电视剧,一边聊微信. 这里我们 ...
- python的shutil模块
shutil模块提供了大量的文件的高级操作.特别针对文件拷贝和删除,主要功能为目录和文件操作以及压缩操作 1.复制文件 def copy(src, dst): """Co ...
- thinkPHP入门之二
thinkphp提供了很多便利的方法,因为php要记的东西太多了,而thinkphp极大简化了这些,让编写过程更加快速. 为了预防那些了解它的人胡乱玩耍,thinkphp提供一个函数_empty 它是 ...
- 【CSS进阶】box-shadow 与 filter:drop-shadow 详解及奇技淫巧
box-shadow 在前端的 CSS 编写工作想必十分常见.但是 box-shadow 除去它的常规用法,其实还存在许多不为人知的奇技淫巧. 喜欢 markdown 版本的可以戳这里. box-sh ...
- Javascript中关键参数this浅析
自从接触javascript以来,对this参数的理解一直是模棱两可.虽有过深入去理解,但却也总感觉是那种浮于表面,没有完全理清头绪. 但对于this参数,确实会让人产生很多误解.那么this参数到底 ...