zoukankan      html  css  js  c++  java
  • 【转载】C++应用引用计数技术

    原帖:http://www.cnblogs.com/chain2012/archive/2010/11/12/1875578.html

    因为Windows的内核对象也运用了引用计数,所以稍作了解并非无用。

    引用计数可以让多个对象共享一个数据,而且免除了跟踪控制权的负担,让对象自己管理自己,当再没有被使用时可以自动删除,也算是一种简易的垃圾回收机制。

    另一方面,如果有N多个相同的对象:○=○=○=○=...=○=○ 这样的做法是臃肿且无聊的,所以一个好的做法就是让对象可以共享这一个数据。既可以节省内存,又可以提高效率让程序负担更少,不用构造和析构这个值对象的拷贝了。

    1 String a, b, c, d, e;
    2 a=b=c=d=e="hello";
    复制代码
    1 String& String::operator=(const String &rhs)
    2 {
    3 if (data==&rhs) return *this; //防止自我赋值
    4   delete [] data;
    5 data = new char[strlen(rhs.data)+1)];
    6 strcpy(data, rhs.data);
    7 return *this;
    8 }
    复制代码

    用图显示的话,即:

    当a被赋予了另外的值,a="world"; 这时候不能删除这个Hello,应外仍然存在bcde,4个对象在共享这个数据;另外,当只有1个对象x在用这个Hello,而x已经超过了其生存期,没有其他对象指向这个Hello的时候,我们需要删除这个Hello确保不发生资源泄漏。这也就意味着引入引用计数后,图将改变成这样:

    • 实现引用计数

    应该是每一个String值对应一个计数数值,而不是String对象对应一个引用计数。接下来,新建一个嵌套类StringValue来保存计数和其跟踪的值。

    复制代码
    String.h
     1 #include <string>
    2
    3  class String {
    4  public:
    5 String(const char *initValue="");
    6 String& String::operator=(const String &rhs);
    7
    8  private:
    9 // StringValue的主要目的是提供一个空间将一个特别的值和共
    10 // 享此值的对象的数目联系起来
    11 struct StringValue //嵌套类,引用计数
    12 {
    13 int refCount; //计数数值
    14 char *data;
    15 StringValue (const char* initValue);
    16 ~StringValue();
    17 };
    18 StringValue *value;
    19 };
    复制代码
    复制代码
    String.cpp
     1 #include "String.h"
    2
    3 String::StringValue::StringValue(const char* initValue)
    4 :refCount(1)
    5 {
    6 data = new char[strlen(initValue)+1];
    7 strcpy(data, initValue);
    8 }
    9
    10 String::StringValue::~StringValue()
    11 {
    12 delete [] data;
    13 }
    14
    15 String::String(const char *initValue)
    16 :value(new StringValue(initValue))
    17 {
    18
    19 }
    复制代码

    而这样做通常会产生一个问题,

    String s1("More Effective C++");

    String s2("More Effective C++");

    将会变成这样的数据结构:

    想办法改进一下:

    复制代码
    控制副本的简单实现
     1 list<string> String::StringValue::independObj; //独立对象
    2  String::StringValue::StringValue(const char* initValue)
    3 :refCount(1)
    4 {
    5 typedef list<string>::iterator lsp;
    6 lsp p = find(independObj.begin(), independObj.end(), string(initValue));
    7 if (p==independObj.end()||independObj.empty())
    8 {//未找到对象,新建
    9   data = new char[strlen(initValue)+1];
    10 strcpy(data, initValue);
    11 independObj.push_back(string(data));
    12 }
    13 else
    14 {
    15 // do something...
    16   }
    17 }
    复制代码

    接下来看下String类的拷贝构造函数

    String::String(const String& rhs) 
    : value(rhs.value)
    {
    ++value->refCount;
    }

    当这样构造2个对象:

    String s1("More Effective C++");

    String s2(s1);

    就会产生这样的数据结构,其代价是非常低廉的,省去了新对象的构造(不必分配新内存和把内容拷贝到这块内存中)和之后的析构(不必释放那块内存),仅仅是使计数+1和拷贝了下指针

    拷贝构造函数之后看下析构函数

    复制代码
    String::~String()
    {
    if (--value->refCount == 0)
    {
    delete value;
    }
    }
    复制代码

    即,当被引用的对象还有其他共享对象时,仅把计数-1;而当没有其他共享对象时,才彻底将引用对象析构掉。接着,是重载赋值操作符,稍微有些复杂

    复制代码
    String& String::operator=(const String &rhs)
    {
    if (value == rhs.value) //赋值的是其本身
    return *this; //什么也不做
    if (--value->refCount == 0) //如果只有当前对象在共享那个数据
    delete value; //则删除掉,因为即将被赋予新的引用。不是的话,仅将计数-1
    value = rhs.value; //赋值操作
    ++value->refCount; //计数器+1
    return *this;
    }
    复制代码
    • 写时拷贝

    const版本的下标操作仅仅是只读的,不会对引用对象做出修改

    const char& String::operator[](int index) const  //const版本
    {
    //需下标溢出检查
    return value->data[index];
    }

    需要考虑的是非const版本的下标操作,因为C++编译器无法告诉我们非const的operator[]是会被用来读还是写操作。所以我们保守地认为所有的操作都是“写”的。

    复制代码
    char& String::operator[](int index)  //非const版本
    {
    if (value.refCount>1) //如果引用对象不止一个
    {
    --value.refCount; //计数减一,相当于把这个引用删除了
    value = new StringValue(value->data); //重新申请一份新的拷贝
    }
    return value->data[index];
    }
    复制代码

    这个思想就是:“与其他对象共享的一个值直到写操作时才拥有自己的拷贝”。即,lazy原则的特例。

    • 指针、引用与写时拷贝

    在大部分情况下都能满足以上的应用,可是唯一情况却颇为棘手,比如

    String s1("More Effective C++");

    char* p=&s1;

    String s2 = s1;

    拷贝构造函数让s2和s1共享这个对象,这时候的数据结构为

    如果写下这样一句: p[1]='X'; //将同时修改s1和s2的内容!String 的拷贝构造函数无法检测出s1拥有指向StringValue指针的存在。该问题的一个解决方法就是:在每个StringValue中增加一个标志,表示该对象是否可以被共享。在最初是ture状态,而在调用了非const的operator[]之后则设置成false,且之后永远置于false状态。

    追加共享标志位的String
    • 带引用计数的基类

    引用计数不仅运用在字符串类上,只要是多个对象共享相同值的类都可以。

    构建一个基类(RCObject),任何需要引用计数的类都必须继承自此类。由RCObject类封装引用计数功能。

    RCObject.h
    RCObject.cpp
    • 自动引用计数处理

    RCObject类给了我们一个存储引用计数的地方,并提供了成员函数供我们操作引用计数,但调用这些函数的动作还必须被手工加入其它类中。仍然需要在String的拷贝构造函数和赋值运算函数中调用StringValue的addReference和 removeReference函数。这很笨拙。

    StringValue *value; 必须操作StringValue对象的refCount字段。是否能够让指针自身检测发生复制拷贝,赋值操作,析构操作此类事件,而对于计数经行修改的操作呢?答案是否定的。代替的方法就是利用智能指针。

    【分析】

    开始看函数式treap的时候看到的...

    话说函数式treap消耗的内存那么大吗?还要用引用计数。

    表示只用内存池。

     顺便附上一份网上函数式treap模板...

    http://ideone.com/kbSjPp

  • 相关阅读:
    数据库事务的四大特性以及事务的隔离级别
    数据库事务
    Java 反射机制(包括组成、结构、示例说明等内容)
    Java 集合系列14之 Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)
    一分钟教你知道乐观锁和悲观锁的区别
    vue-router的history模式发布配置
    asp.net core使用Swashbuckle.AspNetCore(swagger)生成接口文档
    ubuntu下查看-卸载软件(卸载.net core sdk的方法)
    ubuntu终端执行shell脚本报command not found解决方法
    sqlserver 重置标识列
  • 原文地址:https://www.cnblogs.com/hoskey/p/4329332.html
Copyright © 2011-2022 走看看