To ward off mistacks, we can summary some rules of using reference counters.
If a reference counted (RCed) object a field member of another object, or it is contained in some collection filed of another object, we can that object the ‘owner‘.
In the following code snippets, A is owner of RcObj (if RcObj is reference counted)
class A {
RcObj _b;
}
class A {
List<RcObj> _list;
}
With the owner defined, we can define the following rules:- Rule 1: The initial reference counter is 1 for newly allocated objects.
- Rule 2: When owning a reference counted object, we should increase it’s reference counter.
- Rule 3: When losing the ownership, we should decrease the reference counter.
- Rule 4: When an owner’s method (not get property) returns RCed objects, it should increase RCs before returning them. In another words, it shared the ownership.
- Rule 5: When an owner’s get property returns RCed objects, it should not increase RCs.
- Inference 1: After getting a RCed object from a method, we should release it unless we return it to the caller or own it.
class A {
C c;
List<RcObj> list;
void Foo1() {
RcObj obj = c.GetRcObj();
obj.Release(); // decrease reference counter
}
RcObj Foo2() {
RcObj obj = c.GetRcObj();
return obj;
// no need to decrease reference counter
}
void Foo3() {
RcObj obj = c.GetRcObj();
list.Add(obj);
// no need to decrease reference counter
}
RcObj Foo4() {
RcObj obj = c.GetRcObj();
list.Add(obj);
obj.Own(); //increase reference counter
return obj;
}
}
class C {
RcObj GetRcObj() {
return new RcObj();
}
// or
RcObj _obj;
RcObj GetRcObj() {
_obj.Own; // increase RC before method return
return _obj;
}
}
- Inference 2: : After getting a RCed object from a get property, we should not change its RC, unless we return it to the caller or own it.
class A {
C c;
void Foo1() {
RcObj obj = c.RcObj;
Bar(obj);
// don't change is RC in this method, (Bar may change RC)
}
RcObj Foo2() {
RcObj obj = c.RcObj;
obj.Own
// increase RC
return obj;
}
}
class C {
RcObj _obj;
RcObj RcObj {
get {
return _obj;
}
}
}
- Inference 3: We should not change RC of a RCed object passed in as method parameter , unless we want to own it.
class A {
void Foo1(RcObj obj) {
obj.xxx();
obj.yyy();
// don't change RC
}
RcObj _obj;
void Foo2(RcObj obj) {
obj.xxx();
obj.yyy();
this._obj = obj;
obj.Own; // increase RC according to Rule 2
}
RcObj Foo3(RcObj obj) { // bad practice
obj.xxx();
obj.yyy();
obj.Own; // increase RC according to Rule 4
return obj;
}
}
With these rules and inferences, we can write a static code analysis tool to help us eliminate RC related bug.Basic Reference Counter Implementation:
public interface IRefCounter
{
void Own();
void Release();
int RefCount {get;}
}
public sealed class RefCounter : AbstractRefCounter
{
private readonly Action _handler;
public RefCounter(Action handler)
{
_handler = handler;
}
protected override void OnCleanUp()
{
_handler?.Invoke();
}
}
public abstract class AbstractRefCounter : IRefCounter
{
private int _refCount;
protected AbstractRefCounter()
{
Own();
}
public void Own()
{
_refCount++;
}
public void Release()
{
_refCount--;
if (_refCount == 0)
{
OnCleanUp();
}
}
protected abstract void OnCleanUp();
public int RefCount
{
get {
return _refCount;
}
}
}
Sample usage:
public class RefCountedObj : AbstractRefCounter
{
protected override void OnCleanUp()
{
}
}
public class RefCountedObjWithDelegate : IRefCounter
{
private RefCounter _refCounter;
public RefCountedObjWithDelegate()
{
_refCounter = new RefCounter(OnCleanUp);
}
public void OnCleanUp()
{
}
public void Own()
{
_refCounter.Own();
}
public void Release()
{
_refCounter.Release();
}
public int RefCount()
{
return _refCounter.RefCount();
}
}
For more concrete rules, see StaticAnalysisRules.md
For source code, see ObjectPool

0 评论:
Post a Comment