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++知识库 -> C++沉思录 第七章:改进的Handle类 -> 正文阅读

[C++知识库]C++沉思录 第七章:改进的Handle类

一 温故而知新

????????在第六章,为了避免复制,我们设计了Handle类。对于某个特定的类(我们是以Point类为例),它工作的很好,既能体现指针语义,也能提供写时复制。回想类的设计过程,我们为Point类设计了一个中间类UsePoint,用来控制引用计数,并在类内放了一个Point 成员:

class UsePoint{

    //...

    Point p;

};

????????同时在Handle类内放了一个私有数据成员:

class Point_Handle{

    //...

private:

        UsePoint* up;
};

????????你应该发现了一些不方便的地方:每当我们想为一个特定类设计Handle,就要为其写一个新的中间类。

????????再回到第五章,我们用代理类来解决在容器中存储处于继承体系中的类。

????????当我们想为继承体系设计Handle时,记住我们想这样使用:

Handle arr[ 10 ] = { /*......*/ };

????????Handle现在没法工作了,因为我们的Handle只为特定类服务,没有动态绑定的功能,现在就只能在容器中存储某个特定类。

二 问题出在哪里

????????为了触发C++的动态绑定,我们需要使用基类的引用或指针,很显然目前的Handle类并没有:因为使用了中间类UsePoint,在Handle类内存放的是UsePoint*。为了实现我们的目标,我们应该在Handle类内存放基类的指针:

class Point_Handle{

    //...

private:

    Point* p;

};

????????这样,我们可以为Handle传递任意派生类的对象。

三 分离引用计数

? ? ? ? 接下来,由于丢弃了中间类,我们需要再次考虑引用计数。我们当然不能将引用计数放在Point中,因为不想修改Point,而且Point现在是一个继承体系。不过我们可以在将引用计数放在Handle类中,并以指针的形式。

? ? ? ? 还记得第六章我们的讨论吗?Handle类具有指针语义,也就是多个Handle可能指向同一个Point。那么引用计数能否自动同步?我们看看修改后Handle类的拷贝构造函数:

Point_Handle( const Point_Handle& ph ):up( ph.up ),uc(ph.uc){

        ++*uc; 
    }

? ? ? ? 复制的都是一份指针的拷贝,析构时,也可以根据引用计数的值,删除指针,完全满足我们的要求。

????????简而言之,我们需要丢弃中间类,将引用计数分离,并修改Handle类:

class Point_Handle{
public:
    //...
private:
    Point* p;    //基类指针
    int* uc;     //引用计数
};

? ? ? ? 我们看看修改后Handle类的完整定义:

class Point_Handle{
public:
    Point_Handle( ?):up( new Use_Point( ) ),uc(new int(1)){ ?}
    Point_Handle( int x,int y ):up( new Use_Point( x,y ),uc(new int(1)) ){ ?}
    ~Point_Handle( ?)
    {
        --*uc;
        if(*uc == 0)
        {
            delete up;
            delete uc;
        }
    }
    Point_Handle( const Point_Handle& ph ):up( ph.up ),uc(ph.uc){

        ++*uc; 
    }
    Point_Handle( const Point& p ):up( new Use_Point(p) ),uc(new int(1)){ ?}
    Point_Handle& operator=( const Point_Handle& ph ){
        ++*ph.uc;;                             //右侧对象引用计数+1
        if( --*uc == 0 )                       //左侧对象将指向新对象
        {                                      //原引用计数-1
            delete up;                         //如果为0,就析构原来指向的内存
            delete uc;
        }
        up = ph.up;
        uc = ph.uc;                            //现在指向新的副本了
        return *this;
    }
private:
    Point* p;
    int* uc;
};

四 封装引用计数

?????????到目前为止,我们可以用Handle保存处于继承体系的对象了。但是引用计数毫无封装性可言,更别谈之后想要扩充引用计数的功能。

? ? ? ? 我们打算封装引用计数为一个类,那么,它的功能至少应该是这样:

class UseCount{
public:
    UseCount():p(new int(1)){}
    UseCount( const UseCount& uc):p(uc.p){
        ++*p;
    }
    UseCount& operator=(const UseCount& uc){
        //...
    }
    ~UseCount(){
        if(--*p == 0)
        {
            delete p;
        }
    }

private:
    int* p;
};

????????现在重写Handle类。

? ? ? ? 由于引用计数可以依靠默认构造函数,因此Handle类的构造函数变的异常简单:

class Point_Handle{
public:
    //默认构造函数
    Point_Handle():p(new Point()){}
    //构造函数
    Point_Handle(int x,int y):p(new Point(x,y)){}
    Point_Handle(const Point& p0):p(new Point(p0)){}
    //拷贝构造函数
    Point_Handle(const Point_Handle& ph):p(ph.p),uc(ph.uc){}
    //析构函数
    ~Point_Handle(){
        //?
    }
    //拷贝赋值运算符
    Point_Handle& operator=(const Point_Handle& ph){
        //?
    }
private:
    Point* p;        //基类指针
    UseCount uc;     //引用计数
}

? ? ? ? 析构函数和拷贝赋值运算符稍微复杂点。

? ? ? ? 为了析构 Point* p ,我们需要知道UseCount是否是1。因此我们需要为UseCount添加一个成员函数,用来判断UseCount是否为1:

class UseCount{
public:
    //...
    bool only(){
        return *p == 1;
    }
};

? ? ? ? 为了执行Handle类的赋值操作,我们换一种思路来考虑:Handle类的赋值操作包括了两部分:Point* 和 UseCount。Point*依赖于UseCount。

????????赋值操作符左边的UseCount始终被改写。

????????如果左边的类引用计数目前为1,那么被改写后,引用计数(int* uc;)应该被析构,我们就把右边的引用计数赋值给左边;

????????如果左边的引用计数大于1,那么被改写后,仅仅自减,引用计数不需要析构,我们再把右边的引用计数赋值给左边。

????????如果左边的引用计数被析构,意味着左边的Handle类是唯一的控制类,此时我们也应该析构Point*;否则,就不需要析构左边的Point*,做简单的赋值操作就可以。

? ? ? ? 我们可以在UseCount内新增一个成员函数,用来判断当前的引用计数是否被析构:

? ? ? ? 与此同时,UseCount的拷贝赋值运算符应该私有化,因为我们不希望为外界提供该接口,以防止外界随意赋值,修改计数器的值:

class UseCount{
public:
    //...

    bool destruct(const UseCount& u){
        ++*u.p;                //右边先自加
        if(--*p == 0)          //左边自减,如果此时为0,应该析构  
        {
                delete p;      //析构计数器
                p = u.p;       //把右边赋值给左边
                return true;   //告诉外界,析构了
        }
        p = u.p;               //没有析构,直接赋值
        return false;          //未析构        
    }
private:
    UseCount& operator=(UseCount& u){}    //私有化,不允许赋值操作
};

? ? ? ? 有了这些,我们来重写Handle类的析构函数和赋值操作符:

    //析构函数
    ~Point_Handle(){
        if(uc.only()){        //如果计数器为1
            delete p;         //就析构当前对象
        }
    }
    //拷贝赋值运算符
    Point_Handle& operator=(const Point_Handle& ph){
        if(uc.destruct(ph.uc)){    //当前的计数器对象是否将被析构;
            delete p;              //如果是的话,也应该析构Point* p;
        }
        p = ph.p;                  //不是的话,就直接赋值即可,因为左边的Handle类对象仍然存在

        return *this;
    }

五 存取和写入

? ? ? ? 对于写操作,我们仍然使用写时复制实现Handle类的值语义。先看看最开始的实现方法:

? ? ? ? 当我们执行了这样的操作,并进行写值的时候:

Point_Handle h(3,4);
Point_Handle h1;

//复制
h1 = h;

//写对象。为了体现值语义,我们在这里新建一个Point,并让h1控制这个新的Point
//所以h.x() == 3    h1.x() == 5
h1.x(5);

? ? ? ? 所以,我们是这样做的:

    //...其他同...
    Point_Handle& xw(int x0) {    
		if (up->use_count != 1)            //如果引用计数不为1
		{
			--up->use_count;                //递减引用计数
			up = new Use_Point(up->p);      //创建新Point
		}
		up->p.xw(x0);                       //写新Point
		return *this;
	}
    Point_Handle& yw(int y0) {
		if (up->use_count != 1)
		{
			--up->use_count;
			up = new Use_Point(up->p);
		}
		up->p.yw(y0);
		return *this;
	}

? ? ? ? 我们需要判断当前的引用计数是否为1。如果是,那么就直接修改值;如果不是,就自减引用计数,并新建Point,让Handle类控制这个新Point。

? ? ? ? 应用到现在的UseCount类,我们需要一个成员函数,来判断是不是需要创建新的Point:

class UseCount{
public:
    //...

    bool createNewPoint()
    {
        if(*p == 1)            //判断引用计数是否为1
        {
            return false;      //是的话就不用创建
        }
        --*p;                  //不是先自减
        p = new int(1);        //创建新的引用计数,并置1
        return true;           //需要创建新的Point
    }
};

? ? ? ? 那么写操作就应该是这样:

    Point_Handle& xw(int x0) {
		if (uc.createNewPoint())    //如果需要创建新Point
		{
			p = new Point(*p);      //用当前Point的值创建新Point
		}
		p->xw(x0);                  //写新Point对象
		return *this;
	}
    Point_Handle& yw(int y0) {
		if (uc.createNewPoint())
		{
			p = new Point(*p);
		}
		p->yw(y0);
		return *this;
	}

? ? ? ? 对于取操作,就很简单了:

class Point_Handle()
{
public:
    //...

    int xr() const {
        return p->x();
    }

    int yr() const {
        return p->y();
    }
};

六 使用Handle

? ? ? ? 有这样一个继承体系:

#ifndef TEST1_H
#define TEST1_H

#include <iostream>

//base class
class A {
public:
	A() {}
	A(int x0, int y0) :x(x0), y(y0) {}
	virtual void print() {
		std::cout << "x = " << x << "y = " << y << std::endl;
	}
	//虚析构函数
	virtual ~A() {
	}
private:
	int x, y;
};

class B : public A {
public:
	//默认构造
	B() {}
	B(int x0,int y0,std::string na):A(x0,y0),name(na){
	}
	//拷贝构造
	B(const B& b):name(b.name) {
	}
	void print() {
		std::cout << name << std::endl;
	}
	//析构函数
	virtual ~B() {
	}
private:
	std::string name;
};

class C : public A {
public:
	//默认构造
	C() {
	}
	C(int x0, int y0, int age) :A(x0, y0), age(age) {
	}
	//拷贝构造
	C(const C& c) :age(c.age) {
	}
	void print() {
		std::cout << age << std::endl;
	}
	//析构函数
	virtual ~C() {
	}
private:
	int age;
};

class D : public A {
public:
	//默认构造
	D() {
	}
	D(int x0, int y0, std::string ss) :A(x0, y0), s(ss) {
	}
	//拷贝构造
	D(const D& d) :s(d.s) {
	}
	void print() {
		std::cout << s << std::endl;
	}
	//析构函数
	virtual ~D() {
	}
private:
	std::string s;
};


#endif //TEST1_H

? ? ? ? UseCount类:

#ifndef USECOUNT_H
#define USECOUNT_H
#include <iostream>

class UseCount {
public:
	UseCount() :p(new int(1)) {}
	UseCount(const UseCount& uc) :p(uc.p) {}
	~UseCount() {
		if (-- * p == 0) {
			delete p;
		}
	}
	bool only() {
		return *p == 1;
	}

	bool destructor(const UseCount& uc) {
		++* uc.p;
		if (-- * p == 0) {
			delete p;
			p = uc.p;
			return true;
		}
		p = uc.p;
		return false;
	}

	bool createNew() {
		if (*p == 1)
		{
			return false;
		}

		--* p;
		p = new int(1);
		return true;
	}
private:
	int* p;
	UseCount& operator=(UseCount& uc) {}
};
#endif //USECOUNT_H

? ? ? ? 我们希望在容器中存储这个继承的所有派生类,为此提供这个类专属的Handle类:

#ifndef HANDLE_H
#define HANDLE_H

#include <iostream>
#include "UseCount.h"
#include "test1.h"

class Handle {
public:
	Handle():ap(0) {}
	Handle(A* a):ap(a) {
	}
	Handle(const Handle& h):ap(h.ap), u(h.u) {}
	~Handle() {
		if (u.only())
		{
			//delete ap;
		}
	}
	Handle& operator=(const Handle& h) {
		if (u.destructor(h.u)) {
			delete ap;
		}
		ap = h.ap;
		return *this;
	}
	void sprint() {
		ap->print();
	}
private:
	A* ap;
	UseCount u;
};

#endif //HANDLE_H

? ? ? ? ?注意,这几处有所修改:

~Handle() {
		if (u.only())
		{
			//delete ap; //由于存在虚基类,我们为其传入对象指针,因此不需要delete,将由对象负责
		}
	}
Handle(A& a):ap(a.copy()) {
	}

? ? ? ? ?现在,我们可以这样使用了:

#include <iostream>
#include "Handle.h"

void htest() {
	A a(5,7);
	B b(1,2,"yxl");
	C c(3,4,20);
	D d(5,6,"hw");

	Handle arr[4]{&a,&b,&c,&d};
	arr[0].sprint();
	arr[1].sprint();
	arr[2].sprint();
	arr[3].sprint();
}
int main()
{
	htest();
	return 0;
}

? ? ? ? 输出:

x = 5y = 7
yxl
20
hw

F:\yxl\windows_program\Project2\Debug\Project2.exe (进程 19932)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

?七 总结

? ? ? ? 改进的Handle类相比于第六章,完全可以应付处于继承体系下的对象了;并且,不需要再为特定的类创建引用计数类,也不需要考虑内存管理方面的问题。所有的修改操作现在都集中在Handle类来完成。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-24 18:17:43  更:2021-12-24 18:19:00 
 
开发: 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年11日历 -2024/11/24 13:56:20-

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