详解C++句柄类
上一篇文件介绍了关于C++代理类的使用场景和实现方法,但是代理类存在一定的缺陷,就是每个代理类会创建一个新的对象,无法避免一些不必要的内存拷贝,本篇文章引入句柄类,在保持代理类多态性的同时,还可以避免进行不不要的对象复制。
我们先来看一个简易的字符串封装类:MyString,为了方便查看代码,将函数的声明和实现放到了一起。
class MyString { public: // 默认构造函数 MyString() { std::cout << "MyString()" << std::endl; buf_ = new char[1]; buf_[0] = '\0'; len_ = 0; } // const char*参数的构造函数 MyString(const char* str) { std::cout << "MyString(const char* str)" << std::endl; if (str == nullptr) { len_ = 0; buf_ = new char[1]; buf_[0] = '\0'; } else { 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_); } // str1 = str2; const MyString& operator=(const MyString& other) { std::cout << "MyString::operator=(const MyString& other)" << std::endl; // 判断是否为自我赋值 if (this != &other) { if (other.len_ > this->len_) { delete[]buf_; buf_ = new char[other.len_ + 1]; } len_ = other.len_; strcpy_s(buf_, len_ + 1, other.buf_); } return *this; } // str = "hello!"; const MyString& operator=(const char* str) { assert(str != nullptr); std::cout << "operator=(const char* str)" << std::endl; size_t strLen = strlen(str); if (strLen > len_) { delete[]buf_; buf_ = new char[strLen + 1]; } len_ = strLen; strcpy_s(buf_, len_ + 1, str); return *this; } // str += "hello" void operator+=(const char* str) { assert(str != nullptr); std::cout << "operator+=(const char* str)" << std::endl; if (strlen(str) == 0) { return; } size_t newBufLen = strlen(str) + len_ + 1; char* newBuf = new char[newBufLen]; strcpy_s(newBuf, newBufLen, buf_); strcat_s(newBuf, newBufLen, str); delete[]buf_; buf_ = newBuf; len_ = strlen(buf_); } // 重载 ostream的 <<操作符 ,支持 std::cout << MyString 的输出 friend std::ostream& operator<<(std::ostream &out, MyString& obj) { out << obj.c_str(); return out; } // 返回 C 风格字符串 const char* c_str() { return buf_; } // 返回字符串长度 size_t length() { return len_; } ~MyString() { delete[]buf_; buf_ = nullptr; } private: char* buf_; size_t len_; };
看一段测试程序
#include "MyString.h" int _tmain(int argc, _TCHAR* argv[]) { MyString str1("hello~~"); MyString str2 = str1; MyString str3 = str1; std::cout << "str1=" << str1 << ", str2=" << str2 << ", str3=" << str3; return 0; }
输出内容如下:
可以看到,定义了三个MyString对象,str2和str3都是由str1拷贝构造而来,而且在程序的运行过程中,str2和str3的内容并未被修改,但是str1和str2已经复制了str1缓冲区的内容到自己的缓冲区中。其实这里可以做一个优化,就是让str1和str2在拷贝构造的时候,直接指向str1的内存,这样就避免了重复的内存拷贝。但是这样又会引出一些新的问题:
1. 多个指针指向同一块动态内存,内存改何时释放?由谁释放?
2. 如果某个对象需要修改字符串中的内容,该如和处理?
解决这些问题,在C++中有两个比较经典的方案,那就是引用计数和Copy On Write。
在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。
下面给出引用计数的一个封装类:
class RefCount { public: RefCount() : count_(new int(1)){}; RefCount(const RefCount& other) : count_(other.count_) { ++*count_; } ~RefCount() { if (--*count_ == 0) { delete count_; count_ = nullptr; } } bool Only() { return *count_ == 1; } void ReAttach(const RefCount& other) { // 更新原引用计数的信息 if (Only()) { delete count_; } else { --*count_; } // 更新新的引用计数的信息 ++*other.count_; // 绑定到新的引用计数 count_ = other.count_; } void MakeNewRef() { if (*count_ > 1) { --*count_; count_ = new int(1); } } private: int* count_; };
Copy On Write:就是写时复制,通过拷贝构造初始化对象时,并不直接将参数的资源往新的对象中复制一份,而是在需要修改这些资源时,将原有资源拷贝过来,再进行修改,就避免了不必要的内存拷贝。
下面的代码是完整的句柄类MyStringHandle。每一个句柄类,都包含一个引用计数的类,用来管理和记录对MyString对象的引用次数。
class MyStringHandle { public: MyStringHandle() : pstr_(new MyString){} // 这两种参数的构造函数必须构造一个新的MyString对象出来 MyStringHandle(const char* str) : pstr_(new MyString(str)) {} MyStringHandle(const MyString& other) : pstr_(new MyString(other)) {} // 拷贝构造函数,将指针绑定到参数绑定的对象上,引用计数直接拷贝构造,在拷贝构造函数内更新引用计数的相关信息 MyStringHandle(const MyStringHandle& ohter) : ref_count_(ohter.ref_count_), pstr_(ohter.pstr_) {} ~MyStringHandle() { if (ref_count_.Only()) { delete pstr_; pstr_ = nullptr; } } MyStringHandle& operator=(const MyStringHandle& other) { // 绑定在同一个对象上的句柄相互赋值,不作处理 if (other.pstr_ == pstr_) { return *this; } // 若当前引用唯一,则销毁当前引用的MyString if (ref_count_.Only()) { delete pstr_; } // 分别将引用计数和对象指针重定向 ref_count_.ReAttach(other.ref_count_); pstr_ = other.pstr_; return *this; } // str = "abc" 这里涉及到对字符串内容的修改, MyStringHandle& operator=(const char* str) { if (ref_count_.Only()) { // 如果当前句柄对MyString对象为唯一的引用,则直接操作改对象进行赋值操作 *pstr_ = str; } else { // 如果不是唯一引用,则将原引用数量-1,创建一个新的引用,并且构造一个新的MyString对象 ref_count_.MakeNewRef(); pstr_ = new MyString(str); } return *this; } private: MyString* pstr_; RefCount ref_count_; };
看一段测试程序:
int _tmain(int argc, _TCHAR* argv[]) { // 构造MyString MyStringHandle str1("hello~~"); // 不会构造新的MyString MyStringHandle str2 = str1; MyStringHandle str3 = str1; MyStringHandle str4 = str1; // 构造一个空的MyString MyStringHandle str5; // 将str1赋值到str5,不会有内存拷贝 str5 = str1; // 修改str5的值 str5 = "123"; str5 = "456"; return 0; }
总结
本篇文章介绍了C++句柄类的设计思想与简单实现,主要通过引用计数和Copy On Write实现,这两种思想还是很经典的,垃圾回收、智能指针的实现都有借鉴这两种思想。水平有限,可能会有一些错误或者描述不明确,欢迎大家拍砖~~
相关文章
- vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
- 这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
- 本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
- 整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
- 这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
- 这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
- 这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
- 这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
- 这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
- 虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
- 这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
- 这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
- 这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25
- 这篇文章主要介绍了Python获取浏览器窗口句柄过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-26