本文给大家详细分析了C handle类的相关知识点,有需要的朋友可以学习参考一下。
上一个文件介绍了C proxy类的使用场景和实现方法,但是proxy类有一些缺陷,就是每个proxy类都会新建一个对象,无法避免一些不必要的内存拷贝。本文引入了handle类,它可以保持代理类的多态性,避免不必要的对象复制。
我们先来看一个简单的字符串封装类:MyString。为了方便查看代码,函数的声明和实现放在一起。
类MyString
{
公共:
//默认构造函数
MyString()
{
STD:cout ' MyString()' STD:endl;
buf _=new char[1];
buf _[0]=' \ 0 ';
len _=0;
}
constchar *参数的构造函数
MyString(const char* str)
{
STD:cout ' MyString(const char * str)' STD:endl;
if (str==nullptr)
{
len _=0;
buf _=new char[1];
buf _[0]=' \ 0 ';
}
其他
{
len _=strlen(str);
buf _=new char[len _ 1];
strcpy_s(buf_,len_ 1,str);
}
}
//复制构造函数
MyString(const MyString other)
{
STD:cout ' MyString(const MyString other)' STD:endl;
len _=strlen(other . buf _);
buf _=new char[len _ 1];
strcpy_s(buf_,len_ 1,other . buf _);
}
//str 1=str 2;
const MyString运算符=(const MyString other)
{
STD:cout ' MyString:operator=(const MyString other)' STD:endl;
//确定是否是自赋值。
如果(这个!=其他)
{
if (other.len_ this-len_)
{
删除[]buf _;
buf _=new char[other . len _ 1];
}
len _=other.len _
strcpy_s(buf_,len_ 1,other . buf _);
}
返回* this
}
//str='你好!';
const MyString运算符=(const char* str)
{
断言(str!=nullptr);
STD:cout ' operator=(const char * str)' STD:endl;
size _ t strLen=strLen(str);
if (strLen len_)
{
删除[]buf _;
buf _=new char[strLen 1];
}
len _=strLen
strcpy_s(buf_,len_ 1,str);
返回* this
}
//str='hello '
void运算符=(const char* str)
{
断言(str!=nullptr);
STD:cout ' operator=(const char * str)' STD:endl;
if (strlen(str)==0)
{
返回;
}
size _ t new buflen=strlen(str)len _ 1;
char * new buf=new char[new buflen];
strcpy_s(newBuf,newBufLen,buf _);
strcat_s(newBuf,newBufLen,str);
删除[]buf _;
buf _=newBuf
len _=strlen(buf _);
}
//重载ostream的运算符,支持std:cout MyString的输出。
friend std:ostream运算符(std:ostream out,MyString obj)
{
out obj . c _ str();
退了出来;
}
//返回C样式的字符串
const char* c_str()
{
返回buf _;
}
//返回字符串长度
size_t长度()
{
return len _;
}
~MyString()
{
删除[]buf _;
buf _=nullptr
}
私人:
char * buf _;
size _ t len _
};
看一个测试程序。
#包含“MyString.h”
int _tmain(int argc,_TCHAR* argv[])
{
MyString str 1(' hello ~ ~ ');
MyString str2=str1
MyString str3=str1
std:cout 'str1=' str1 ',str2=' str2 ',str3=' str3
返回0;
}
输出内容如下:
如您所见,定义了三个MyString对象。str2和str3是通过复制str1构造的,str2和str3的内容在程序运行过程中没有被修改,但是str1和str2已经将str1缓冲区的内容复制到了自己的缓冲区中。其实这里可以做一个优化,就是str1和str2在复制和构造的时候可以直接指向str1的内存,这样就避免了重复的内存复制。但这将导致一些新的问题:
1.多个指针指向同一个动态内存。什么时候释放内存?谁发布的?
2.如果一个对象需要修改字符串中的内容,它应该像和那样做什么?
针对这些问题,C中有两个经典的解决方案,即引用计数和写时复制。
在引用计数中,每个对象负责维护该对象的所有引用的计数值。当一个新的引用指向一个对象时,引用计数器递增,当一个引用被移除时,引用计数器递减。当引用计数达到零时,对象将释放被占用的资源。
以下是具有引用计数的封装类:
类别引用计数
{
公共:
ref count():count _(new int(1)){ };
RefCount(const RefCount other):count _(other . count _)
{
* count _;
}
~RefCount()
{
if ( - *count_==0)
{
删除计数_;
count _=nullptr
}
}
仅限布尔()
{
return * count _==1;
}
void重附属(const RefCount other)
{
//更新原始引用计数的信息
if(仅())
{
删除计数_;
}
其他
{
-* count _;
}
//更新新引用计数的信息
* other . count _;
//绑定到新的引用计数
count _=other.count _
}
void MakeNewRef()
{
if (*count_ 1)
{
-* count _;
count _=new int(1);
}
}
私人:
int * count _;
};
文案:写的时候抄。通过复制初始化对象时,参数的资源不会直接复制到新对象中。当需要修改这些资源时,先复制原始资源,然后再进行修改,这样就避免了不必要的内存复制。
下面的代码是完整的句柄类MyStringHandle。每个handle类都包含一个引用计数类,用于管理和记录对MyString对象的引用数量。
MyStringHandle类
{
公共:
MyStringHandle() : pstr_(新的MyString){}
//这两个参数的构造函数必须构造一个新的MyString对象。
MyString handle(const char * str):pstr _(new MyString(str)){ }
MyString handle(const MyString other):pstr _(new MyString(other)){ }
//复制构造函数,将指针绑定到参数绑定的对象,直接复制引用计数,更新复制构造函数中引用计数的相关信息。
MyStringHandle(const MyStringHandle ohter):ref _ count _(ohter . ref _ count _),pstr_(ohter.pstr_) {}
~MyStringHandle()
{
if (ref_count_。仅())
{
删除pstr _;
pstr _=nullptr
}
}
MyStringHandle运算符=(const MyStringHandle other)
{
//绑定到同一个对象的句柄不经处理就相互赋值。
if (other.pstr_==pstr_)
{
返回* this
}
//如果当前引用是唯一的,销毁当前引用的MyString。
if (ref_count_。仅())
{
删除pstr _;
}
//分别重定向引用计数和对象指针
ref_count_。重新挂接(other . ref _ count _);
pstr _=other.pstr _
返回* this
}
//str='abc '这涉及到字符串内容的修改,
MyStringHandle运算符=(const char* str)
{
if (ref_count_。仅())
{
//如果当前句柄是对MyString对象的唯一引用,直接改变对象进行赋值。
* pstr _=str
}
其他
{
//如果不是唯一引用,创建一个新的引用,用原来的引用号-1构造一个新的MyString对象。
ref_count_。make new ref();
pstr_=新的MyString(str);
}
返回* this
}
私人:
MyString * pstr _;
RefCount ref _ count _
};
看一个测试程序:
int _tmain(int argc,_TCHAR* argv[])
{
//构造MyString
MyStringHandle str 1(' hello ~ ~ ');
//不会构造新的MyString
MyStringHandle str2=str1
MyStringHandle str3=str1
MyStringHandle str4=str1
//构造一个空的MyString
MyStringHandle str5
//将str1赋给str5,不会有内存复制
str5=str1
//修改str5的值
str5=' 123
str5=' 456
返回0;
}
摘要
介绍了C handle类的设计思想和简单实现,主要通过引用计数和写时复制来实现。这两个思路还是很经典的,垃圾回收和智能指针可以借鉴。水平有限,可能会有一些错误或者描述不清。欢迎拍砖~ ~
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。