温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C++句柄类的示例分析

发布时间:2021-06-17 09:27:03 来源:亿速云 阅读:207 作者:小新 栏目:编程语言

这篇文章给大家分享的是有关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; }

输出内容如下:

C++句柄类的示例分析

可以看到,定义了三个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++句柄类的示例分析

感谢各位的阅读!关于“C++句柄类的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

c++
AI