实现一个固定类(String)的引用计数类StringRc
(不允许使用泛型,所以只能支持特定类型)。
StringRc
的功能是通过引用计数来管理String
的资源,所以String
本身存在需要在堆上(不理解后面解释吧)。StringRc
可以进行clone
签出新的不可变引用(此处不可变引用为设计层面,实际代码中我们无法做到不可变)。
实现
String
类
设计一个String
类,其中需要实现String
类的析构、拷贝构造(深拷贝)。
不要看代码,看我!
没时间解释了,我们只看最重要的。
String
中定义一个字符串缓冲区m_pBuffer
用来模拟管理一个堆内存。String::String(const char* str)
是为了我们能从一个字符串字面值便捷的创建一个String
对象。- 析构和默认构造没什么好说的,创建和销毁我们没有做特别的事情。
String::String(const String& str)
我们定义了一个拷贝构造函数,它的作用是以str
为原型,创建一个新的String
对象,两者资源不共享。(将str
中的资源,深拷贝到新的String
对象)。
StringRc
类
StringRc
的功能是持有一个String
类的资源,并且能通过clone
方法进行签出新的StringRc
对象。
通过StringRc A
对象签出的StringRc B
对象,两者共同持有一个String
对象的资源。在设计层面我们不对外提供这个String
对象的成员(不public
这个String
对象成员),来达到StringRc
为一个资源的多个不可变引用。
解析
- 我们定义了一个公共构造方法
StringRc::StringRc(String&& str)
。- 为什么我们要使用
String
的右值引用来构造StringRc
?答:因为在设计上,我们希望StringRc
获取这个String
的所有权,也就是将原来的String
变为只有StringRc
对象持有,所以我们需要用显式的右值引用来告诉开发者,我们需要你资源的所有权。这样我们在各个StringRc
的对象之间共享String
资源的时候不会发生:String
资源的意外释放。(不转移所有权时,有没有可能有一个逻辑去主动释放String
所持有的资源?有可能!)String
资源的意外修改。(理由同上,我们无法保证不存在外部的修改。我们不希望有外部的修改,因为这对于StringRc
来说是一个黑盒操作,我们的所有引用计数指向的资源被意外的修改,这是反直觉的。)
- 我们在构造中使用了
String(str)
来深拷贝这个资源,目的是为了将String
本身移动到堆,因为我们并不清楚这个资源本身是存在堆还是栈,如果是栈,那么这个资源会被栈主动回收。当String
本身被回收时,如果我们的StringRc
没有被回收,就会出现悬垂引用。- 我们用生命周期来解释这个操作。因为
StringRc
来保存String
的引用,所以String
本身一定要比所有的StringRc
活得更久!生命周期的重要规则:被引用者的生命周期一定要大于等于(或者不小于)引用者本身的生命周期。在实际应用中,我们不清楚StringRc
本身的生命周期,它有可能要跨多个函数,所以一定要保证String
不会被意外释放!
- 我们用生命周期来解释这个操作。因为
- 为什么我们要使用
- 接下来看我们的
static StringRc clone(StringRc& strRc);
方法。clone
是一个静态,设计目的是为了以StringRc& strRc
这个智能指针为源,签出一个新的StringRc
对象,让这两个StringRc
同时指向一个String
资源对象。- 在
clone
中,我们使用了拷贝构造方法StringRc(const StringRc& origin);
,新签出的StringRc
对象,浅拷贝strong_count
和strRef
,并使得strong_count
强引用计数器进行增加1。
- 最后我们看智能指针对象的析构方法
~StringRc();
。- 析构方法很好理解,每当一个
StringRc
对象进行析构的时候,我们会通过减少它们公共的强引用计数器来进行无损耗析构。最终在所有引用都被销毁时(强引用计数器为0),手动执行资源String
的析构,来释放堆内存。
- 析构方法很好理解,每当一个
main
函数开用!
内存就不看了吧,相信你已经人机合一,代码眼上走,内存心中留。
冒充写时拷贝
这章已经难以实现了,为什么?
理想情况下我们应该这么使用LazyCopy
对象:
- 如果我们托管一个
LazyCopy
类用来包裹String
,那么我们的StringRc
必须接受一个泛型T where LatyCopy||String
,泛型T
必须接收LazyCopy
或者String
两个不同对象,不使用泛型语法的情况下,我们很难实现。使用c语法强转?算了吧我不想写屎山。 - 不允许使用重载,不允许使用泛型。
man what can i say!
综上所述,我们如果想将写时拷贝与String
解耦实现。那么我们必须在可以重载、泛型的情况下。所以,我们现在只能冒充一下写时拷贝。
get_mut_rc
方法将此StringRc
对象从此计数中移除,并创建新的计数。同时深拷贝String
资源对象,实现资源完全分离。
上面的代码也好理解。
- 我们先通过
strRc
创建了一个引用计数。并clone
出来一个strRc2
此时,两者的引用计数都为2,且指向同一资源。 - 执行
strRc2.get_mut_rc()
方法,将strRc2
从原有的引用计数中移除,并创建新的引用计数和资源。此时strRc
和strRc2
的引用计数都为1(但不是同一个引用计数器)。 - 通过
strRc2
签出strRc3
,此时strRc2
和strRc3
引用计数都为2,并且指向同一个String
资源。而strRc
引用计数不变,仍为1
。 - 最后,所有的引用计数都被栈回收,
strRc
的String
资源和strRc2、strRc3
的String
资源都被回收,执行两次String
的析构方法。
写前豪言壮语,写的时候才意识到没法使用泛型和运算符重载,只能悻悻而归。