第九十二讲 再谈引用计数



时隔半年之久,这一讲我们再来谈谈引用计数的问题,在谈这个问题之前,前面我们说的一个例子还有一点忘记提醒大家,那就是由于我们使用计时器来控制编辑框和用户的更新,所以如果遇到高并发的时候可能会反应不过来,不过没关系,我们只需要修改计时器的间隔就能满足,这个问题就留给大家吧,好吧,我们还是进入这一讲的主题,这一讲我们来解答上一讲留下的问题,在说之前我囔一句,我们的C++内容已经说了差不多了快一年了,所以以后的东西我都当大家能够理解得过来的,就像前面我们讲的那个聊天服务器,除了一些简单的界面编写使用了大家可能还没接触到的Qt之外,我们几乎都用标准库的内容来完成,而标准库是我和大家说得最多的,所以我都当大家能够理解得过来,当然,我可能没有说得面面俱到,而且我也承认这里的C++部分的内容确实说得有些深,很多技术大家可能看一般国内编写的C++书籍是看不到的,所以在这里也顺便给大家推荐一个书单,也算是我们开这门课的一个补充(应该说这门课其实是大家学习C++的一个简要总结更贴切一些,因为这里面和大家说的东西你们都会在下面的书单中找到原型,我只不过是将他们总结整理出来给大家而已)。

//===============================
对于初学者,不具备C或者C++基础的:
《C++ prime plus》
这本书是完完全全的给初学者用的,网上可以下到第六版(最新版)
如果有一定的基础,知道类的概念,那么可以直接从下面这本入手:
《C++ prime》
当然,就算你熟读上面两本书,可能你还是写不出一个C++程序,所以:
《C++程序语言设计》这本书是必不可少的,不管你基础怎么样,这本书还是必须要读一读的,就冲着我们学的是C++,所以就应该读这本书,因为这是C++之父亲自写的。
能够将这三本书(确切点说应该算两本,如果读过第一本的话,第二本可以直接略过直接看第三本)那么你对C++,标准库也有一定的了解了,也能够知道怎么去写一些算是合格的C++程序,好吧,这时候你可以尝试看一下:
《Effective C++》第三版。
虽然说《C++程序语言设计》里面总结了Effective C++里面的大部分经验和给出了大量的忠告,但是亲自读一读Effective C++你会发现很多宝贵的东西。
关于《Effective STL》这本书,很多人说如果想要高效实用STL,这本书要读,这本书我看过两遍,但是发现如果你能够把C++程序语言设计弄懂的话,这本书可以没有必要看,当然闲来没事翻翻几遍也是可以得。
至于《More Effective C++》这本书有空的话可以看看,因为我觉得如果你把前面几本都弄熟了,这本书里面可取之处真心不多,不过还是有些地方值得思考的,就比如说接下来我们将会看到一个有趣的例子。
《C++沉思录》大家有兴趣的话可以去看看,里面说的东西很深,可以说如果理解透了,里面的技巧可以运用到很多库的开发商,我给大家展示的引用计数手法就是来源这本书的经验。
《深度探索C++对象模型》同上有兴趣的话可以去研究一下,理解编译器是怎样操纵你写的代码的。
ok,关于书,目前就这些吧,因为给大家说的东西都是在这些书上学到的,大家可以看看,也顺便检查一下有些东西是不是我给理解错了。

=====================================

好吧,现在我们来进入正题吧,权当大家能够理解下面的代码(以后的也一样),如果真有不懂,可以提出来,我会酌情考虑。

首先,我用我们以前说的引用技术手法来写一个带引用计数的string(如果大家忘记引用计数了,可以回头去复习一下)
//================================
//mystring.h

#include <iostream>
class mystring;

class Count{

friend class mystring;



Count();



~Count();



Count(const Count&

);



Count&

operator=(const Count&

);


void decount();



bool retach(const Count&

);



bool onlyone();



int* count;


};

class mystring{
public:

mystring(const char* str = "

"

);



mystring(const mystring&

);



mystring&

operator=(const mystring&

);



~mystring();

const void* getAddr() const;



const char operator[](unsigned) const;



char operator[](unsigned);

friend std::ostream&

operator<<(std::ostream&

, const mystring&

);


private:

char* m_data;



Count u_count;


};


===================================

作为一个例子,我们只需这点功能吧,大家应该看得出,这是我一贯使用的手法,所以这实现起来也很简单,所以我就直接给出实现代码:
//=================================
//mystring.cpp
//=======Count=============

Count::Count() :count(new int(1)){
}

Count::Count(const Count&

c){

++*c.count;



count = c.count;


}

Count&

Count::operator=(const Count&

c){

++*c.count;



if (–(*count) == 0){

delete count;


}

count = c.count;


return *this;


}

void Count::decount(){

–*count;


}

bool Count::retach(const Count&

c){

++*c.count;



if (–*count == 0){

delete count;


count = c.count;


return true;


}

count = c.count;


return false;


}

bool Count::onlyone(){

return *count == 1;


}

Count::~Count(){

if (–*count==0)

delete count;


}

//=====mystring===============
mystring::mystring(const char* str){

m_data = new char[strlen(str) + 1];



strcpy_s(m_data, strlen(str) + 1, str);


}

mystring::mystring(const mystring&

str):u_count(str.u_count){

m_data = str.m_data;


}

mystring&

mystring::operator=(const mystring&

str){

if (u_count.retach(str.u_count)){

delete[] m_data;


}

m_data = str.m_data;


return *this;


}

mystring::~mystring(){

if (u_count.onlyone())

delete[] m_data;


}

const void* mystring::getAddr() const{

return reinterpret_cast<void*>(m_data);


}

const char mystring::operator[](unsigned index) const{

if (index >= strlen(m_data))

throw "

overrange index!!!"

;


return m_data[index];


}

char mystring::operator[](unsigned index){

if (u_count.onlyone())

return m_data[index];



u_count.decount();


return mystring(m_data)[index];


}


ostream&

operator<<(ostream&

os, const mystring&

str){

os <<str.m_data;


return os;


}
======================================
可以看出来我的方法中规中矩,没什么亮点,除了比直接在mystring里面使用int好些外,他确实很平庸的,但是效率却算不错,因为他只是简单的封装了一层计数,下面我们来看看另一种方法,这种方法来源于More Effective C++,在封装继续这一块他做得很好,好吧,直接上代码:
//====================================
//MyString.h

class MyString;


class StringData;


#include <iostream>
using std::ostream;

//需要一个模板类,这是核心部分,其实这是一个智能指针
template<typename T>
class RcPtr{
public:

RcPtr(T* p) :ptr(p){

init();


}

RcPtr(const RcPtr&

p) : ptr(p.ptr){

init();


}

RcPtr&

operator=(const RcPtr&

p){

if (ptr != p.ptr){

if (ptr){

ptr->RemRc();


ptr = p.ptr;


init();


}

}

return *this;


}

~RcPtr(){

if (ptr){

ptr->RemRc();


}

}

T* operator->() const{

return ptr;


}

T&

operator*() const{

eturn *ptr;


}

operator char*() const{

return ptr->m_data;


}

private:

T* ptr;


void init(){

if (ptr == 0)

return;


if (ptr->isShareable() == false){

ptr = new T(*ptr);


}

ptr->AddRc();


}
};

//这是一个只能在堆上创建对象的类,专门用来作为基类使用的,看了下面的成员,我们不难看出,上面的模板其实封装了我们下面的基类
class RcObj{
public:

void AddRc();


void DecRr();


void RemRc();


void MarkUnshareable();



bool isShareable();



bool isShared();


private:

int u_refcount;



bool b_shared;


protected:

RcObj();



RcObj(const RcObj&

);



RcObj&

operator=(const RcObj&

);



virtual ~RcObj();


};

//这里书上用的嵌套类,我比较喜欢分块,所以这里被独立出来,不过他的效果和原书上面的嵌套类一样,和上面的mystring一样,虽然我将这个类暴漏出来,但是用户不能使用这个类的,所以满足C++的封装性。
//因为不论是核心的模板类还是我们要展现给用户的string,都需要访问下面的成员,所以我们将这两个类成为该类的友元类

class StringData :public RcObj{

friend class MyString;



friend class RcPtr<StringData>;



char* m_data;



StringData(const char* str = "

"

);



StringData(const StringData&

);


void init(const char* str);



~StringData();


};

//最终我们需要完成的东西就这么一点,如果你没有看出其中奥妙,那么你该回去再好好复习一下C++的基础知识
class MyString{
public:

MyString(const char* str = "

"

);



const char&

operator[](unsigned) const;



char&

operator[](unsigned);



const void* getAddr() const;



friend std::ostream&

operator<<(std::ostream&

, const MyString&

);


private:

RcPtr<StringData>m_value;


};


=================================

ok,一些比较棘手的东西我已经在头文件中注释出来,所以下面我们来看看实现:
//===============================
//M有String.cpp
//=====class RcObj========

RcObj::RcObj():u_refcount(0),b_shared(true){

}

RcObj::RcObj(const RcObj&

r) : u_refcount(0), b_shared(true){

}

RcObj&

RcObj::operator=(const RcObj&

r){

return *this;


}

RcObj::~RcObj(){

}

void RcObj::AddRc(){

++u_refcount;


}

void RcObj::DecRr(){

–u_refcount;


}

void RcObj::RemRc(){

if (–u_refcount == 0)

delete this;


}

void RcObj::MarkUnshareable(){

b_shared = false;


}

bool RcObj::isShared(){

return u_refcount >1;


}

bool RcObj::isShareable(){

return b_shared;


}

//=====class StringData==========

void StringData::init(const char* str){

m_data = new char[strlen(str) + 1];



strcpy_s(m_data, strlen(str) + 1, str);


}

StringData::StringData(const char* str){

init(str);


}

StringData::StringData(const StringData&

sd){

init(sd.m_data);


}

StringData::~StringData(){

delete[] m_data;


}


//====class MyString============

MyString::MyString(const char* str):m_value(new StringData(str)){

}

const char&

MyString::operator[](unsigned index) const{

return m_value->m_data[index];


}

char&

MyString::operator[](unsigned index){

if (m_value->isShared()){

m_value = new StringData(m_value->m_data);


}

m_value->MarkUnshareable();


return m_value->m_data[index];


}

const char MyString::at(unsigned index) const{

return m_value->m_data[index];


}

const void* MyString::getAddr() const{

return m_value->m_data;


}

ostream&

operator<<(ostream&

os, const MyString&

str){

os <<str.m_value;


return os;


}
===============================

实现起来可能比大家想象的都简单,如果你怀疑这个MyString是否能够工作的话,那么我可以告诉,他不但能够工作,而且功能得相当好:
//===========test MyString======
MyString str("

Hello"

);


MyString str1 = str;


MyString str2;


str2 = str;


cout <<str <<endl <<str1 <<endl <<str2 <<endl;


cout <<str.getAddr() <<endl <<str1.getAddr() <<endl <<str2.getAddr() <<endl;


str2[2] = 'w';


cout <<str <<endl <<str1 <<endl <<str2 <<endl;


cout <<str.getAddr() <<endl <<str1.getAddr() <<endl <<str2.getAddr() <<endl;


=================================

第九十二讲 再谈引用计数
我们从运行结果看出来,在没有更改str2[2]之前,str,str1,str2共享一块内存块,当我们修改str2[2]之后,str2就被独立出来,是不是工作得挺好呢?大家可能还在疑惑,为什么我们只给了MyString一个构造函数,为什么他能够完成这么多工作,好吧,这个问题就留给大家去思考。

现在我们再来看一个问题,str2[2] = 'w'改变了str2的内存布局,这其实是我以前给大家说的写时复制技术,现在我们终于派上用场了,但是问题来了,为什么怎么知道[]就要写呢?我们确实不知道,所以目前我只好将非const的[]都使用写时复制技术,如果一开始我们就打算不对str使用[]那么我们可以考虑将他们声明为const的,对于想要获取某个字符,我们可以像标准库一样考虑使用一个at()成员来获取,当然,这个问题还是留给大家去思考吧(因为真的很简单)。

关于判断[]是读还是写,通常我们没有好办法,但是我们可以通过一个代理类判断读和写,这个代理类看起来像下面这样:
//============charProxy=========
class charProxy{
public:

charProxy(MyString&

str,int index);



charProxy&

operator=(const charProxy&

);



charProxy&

operator=(const char);



operator char() const;


private:

MyString m_str;



int
m_index;


};


================================

通过这个代理,我们就能够很好的判断读和写了,当我们使用=号时,那就是写,这时就得重新分配内存,当我们只是简单的读时(也就是使用operator char()),就不用分配空间。

现在我们看出代理确实是个好东西,但是他不是一个解决所有问题的好东西,加入我们要写一个表示N维空间的类,如下所示,加入NDimension表示的一个2维空间的类,那么:
//===============================
NDimension N;


N[0][0]表示2维空间中的第一个数据。
=================================

那么我们想要实现这种方案,可能第一印象想到的就是代理,确实代理能够很好的处理两维的情况,就算是三维也都还好,最多也就是代理再代理而已嘛,那四维呢?代理代理在代理……,ok了,我想你们可能都要被自己弄疯了,这时可能你们会想,使用模板:
==================================
template<typename T>
class Test1D{

//====具体略=======
};


===================================

是的,模板可以解决这个问题,我们可以如下使用:
//==========================
Test1D<int> one1D;

这个表示1D
Test1D<Test<int>>two2D;

现在表示2D
Test1D<Test<Test<int
>>>three3D;

3D
…….
Test1D<Test1D<…….
>>>…>;

…..我想你终究还是发疯了。
============================
面对两种方法你最后都输给了现实,好吧,给大家提示一下,其实面对这种问题,我们可能使用递归来解决?什么递归?递归类?好像没见过,类的递归可能你们没见过,不过没关系,下一讲我们来展示一下类模板的递归(这些都是C++里面很高级的知识了,如果大家对模板不是太熟,可以先温习温习一下)

好吧,今天的东西说得比较多,大家好好消化一下吧,尤其是第二种引用计数的方法,他比我管用的高明不少,大家好好理解吃透吧。

===============================
回复D或者d直接查看目录,回复数字查看相应的章节

原文始发于微信公众号(

C/C++的编程教室

):第九十二讲 再谈引用计数

|

发表评论