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、以对象管理资源的更多相关文章

  1. 《Effective C++》学习笔记条款13 以对象管理资源

    条款 13 :以对象管理资源 例:      voidf()      {           Investment *pInv = createInvestment();           ... ...

  2. Effective C++(13) 用对象管理资源

    问题聚焦: 从这条准则开始,都是关于资源管理的. 资源,一旦用了它,将来必须还给系统. 本条准则,基于对象的资源管理办法,建立在C++的构造函数,析构函数和拷贝函数(拷贝构造函数和重载赋值操作符)的基 ...

  3. 读书笔记 effective c++ Item 13 用对象来管理资源

    1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...

  4. Effective C++ 条款13/14 以对象管理资源 || 在资源管理类中小心拷贝行为

    三.资源管理       资源就是一旦你使用了它,将来不用的时候必须归还系统.C++中最常用的资源就是动态内存分配.其实,资源还有 文件描述符.互斥器.图形界面中的字形.画刷.数据库连接.socket ...

  5. effective C++ 读书笔记 条款14 以对象管理资源

    如果我们使用一个投资行为的程序库: #include "stdafx.h" #include <iostream> #include <memory> us ...

  6. [Effective C++ --013]以对象管理资源

    这一节基本讲述的是将资源放进管理对象,防止忘记释放资源. 1.一般New和Delete使用场景 void fun() { SimpleClass* pSimpleClass1 = new Simple ...

  7. 以对象管理资源——C++智能指针auto_ptr简介

    auto_ptr是C++标准库提供的类模板,它可以帮助程序员自动管理用new表达式动态分配的单个对象.auto_ptr对象被初始化为指向由new表达式创建的对象,当auto_ptr对象的生命期结束时, ...

  8. Effective C++ ----以对象管理资源

    以对象管理资源 通过对象的析构函数的自动调用来自动释放资源 第一部分:几种典型的以对象管理资源的例子 1. STL::auto_ptr 获取资源后立刻放入资源管理对象 std::auto_ptr< ...

  9. 《JavaScript权威指南》学习笔记 第三天 找个对象

    现实生活中真的对象没有找到,在JavaScript 里左一个对象又一个对象,搞的我也是晕晕乎乎不知所云.人事复杂,人心难懂.我虽然是文科生,但是也不善于巧言.还是在js里面找找对象吧.那么我们今天就从 ...

随机推荐

  1. 企业IT管理员IE11升级指南【4】—— IE企业模式介绍

    企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...

  2. ARM的常数表达式

    ARM的常数表达式   如果说Intel指令中的立即数,相信大家都很熟悉.类似的,Arm指令中的“立即数”就是常数表达式.之所以称为常数表达式,而不称为立即数是有原因的. Intel指令属于CISC指 ...

  3. nginx小记

    上一次折腾nginx还是两年前的事情了.好多配置都忘记了. 捣腾了下阿里云,部署了一下,遇到几个小问题,温故并记录一下吧 :) 重新设置 nginx遇到问题:nginx: [error] invali ...

  4. EF:split your EDMX file into multiple diagrams

    我们可以把一个EDMX文件划分为多个类图: 1.在VS中打开EDMX设计器: 2.切换到“模型浏览器”属性设置窗口: 3.在diagrams上右键菜单中选择“添加新的关系图”: 4.在原来的关系图上可 ...

  5. 关于如何在github上创建团队开发环境

    今天想写个如何在github上创建团队开发环境的博客.送给那些还不知道如何在github上创建团队开发环境的开发人员. 1.首先,当然你要有个github的账号.具体怎么注册我这里就不说了.可以上gi ...

  6. API调试工具推荐 - httpie

    API调试工具推荐 - httpie <HelloGitHub>第07期上面看到这个python项目,好东西 文档地址 但是安装的时候报错,google之后发现是个已知的bug,直接使用p ...

  7. 【转载】如何自学深度学习技术,大神Yann LeCun亲授建议

    编者按:Quora 上有网友提问:自学机器学习技术,你有哪些建议?(What are your recommendations for self-studying machine learning), ...

  8. ArchLinux安装指南

    将ArchLinux作为进阶Linux发行版,主要看重滚动更新和深入理解Linux的安装过程. 由于是新手,所以先选择在公司电脑上用VMware来安装.然后渐进到借助U盘在win10笔记本上安装双系统 ...

  9. TSql 巧用Alt 键

    1,查看表的信息 在TSql 编辑器中,选中一个表,如图 点击Alt+F1,就可以查看表的属性定义 2,使用alt批量插入逗号 在Tsql中使用 in 子句,在(value_List)列表中,经常有很 ...

  10. 深入理解DOM事件类型系列第四篇——剪贴板事件

    × 目录 [1]定义 [2]对象方法 [3]应用 前面的话 剪贴板操作可能看起来不起眼,但是却十分有用,可以增强用户体验,方便用户操作.本文将详细介绍剪贴板事件 定义 剪贴板操作包括剪切(cut).复 ...