开发者

Is there a boost::shared_ptr<T> equivalent in C#?

Just curious, I've been using boost:shared_ptr'开发者_JAVA技巧s in the past a LOT - for having multiple objects store a shared pointer to a single object etc.

Is there an equivalent of this functionality in C#?


boost::shared_ptr allows for reference counted pointers in an environment which is not garbage collected. The .NET runtime allows for full garbage collection, so there is no need for a reference counting pointer container - simply store your object references.


The GC eliminates the need for shared pointers when it comes to memory management, but it doesn't do this for resource management.

IDisposable is .NET's solution for resource managemenet, but it doesn't allow shared ownership semantics. See this article for a thorough discussion of the weaknesses.

Here's a simple implementation of a SharedRef, which follows the boost::shared_ptr pattern of a heap allocated reference count object. Not sure how useful it will be yet, but feel free to comment and improve it...

/// <summary>
/// SharedRef class, which implements reference counted IDisposable ownership.
/// See also the static helper class for an easier construction syntax.
/// </summary>
public class SharedRef<T> : IDisposable
    where T:class,IDisposable
{
    private SharedRefCounter<T> _t;

    /// <summary>
    /// Create a SharedRef directly from an object. Only use this once per object.
    /// After that, create SharedRefs from previous SharedRefs.
    /// </summary>
    /// <param name="t"></param>
    public SharedRef(T t)
    {
        _t = new SharedRefCounter<T>(t);
        _t.Retain();
    }

    /// <summary>
    /// Create a SharedRef from a previous SharedRef, incrementing the reference count.
    /// </summary>
    /// <param name="o"></param>
    public SharedRef(SharedRef<T> o)
    {
        o._t.Retain();
        _t = o._t;
    }

    public static SharedRef<T> Create(T t)
    {
        return new SharedRef<T>(t);
    }

    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            if (_t != null)
            {
                _t.Release();
                _t = null;
            }
        }

        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
    }

    public T Get()
    {
        return _t.Get();
    }
}

/// <summary>
/// Static helper class for easier construction syntax.
/// </summary>
public static class SharedRef
{
    /// <summary>
    /// Create a SharedRef directly from an object. Only use this once per object.
    /// After that, create SharedRefs from previous SharedRefs.
    /// </summary>
    /// <param name="t"></param>
    public static SharedRef<T> Create<T>(T t) where T : class,IDisposable
    {
        return new SharedRef<T>(t);
    }

    /// <summary>
    /// Create a SharedRef from a previous SharedRef, incrementing the reference count.
    /// </summary>
    /// <param name="o"></param>
    public static SharedRef<T> Create<T>(SharedRef<T> o) where T : class,IDisposable
    {
        return new SharedRef<T>(o);
    }
}

/// <summary>
/// Class which holds the reference count for a shared object.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class SharedRefCounter<T> where T : class,IDisposable
{
    private int _count;
    private readonly T _t;

    public T Get()
    {
        return _t;
    }

    public SharedRefCounter(T t)
    {
        _count = 0;
        _t = t;
    }

    /// <summary>
    /// Decrement the reference count, Dispose target if reaches 0
    /// </summary>
    public void Release()
    {
        lock (_t)
        {
            if (--_count == 0)
            {
                _t.Dispose();
            }
        }
    }

    /// <summary>
    /// Increment the reference count
    /// </summary>
    public void Retain()
    {
        lock (_t)
        {
            ++_count;
        }
    }
}

Notes:

  1. To ensure there's only one reference count for each shared object, make sure you only create 1 SharedRef directly from the object, and after that, create new SharedRefs from previous SharedRefs. This is the same for boost::shared_ptr. It would be nice to add some protection to the class in case you forget this.
  2. It seems a shame that the SharedRef itself has to be a reference type, allocated on the heap, but I think this is the only way of making it Disposable.
  3. I haven't implemented the equivalent of weak_ptr yet, but I think that could easily be added.
  4. It's thread safe (I think), but could probably be more efficient as it uses locks.

Here's some test code showing it in action across 3 threads.

[TestFixture]
public class SharedRefTest
{
    public class MyDisposable : IDisposable
    {
        private bool _disposed = false;
        private string _id;

        public MyDisposable(string id) { _id = id; }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    Console.WriteLine("{0}: Disposing {1}", Thread.CurrentThread.ManagedThreadId, _id);
                }

                _disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        public override string ToString()
        {
            return _id;
        }
    }

    [Test]
    public void Run()
    {
        Task t1, t2, t3;

        // create 2 objects
        Console.WriteLine("{0}: starting initial scope", Thread.CurrentThread.ManagedThreadId);
        using (var o1 = SharedRef.Create(new MyDisposable("o1")))
        using (var o2 = SharedRef.Create(new MyDisposable("o2")))
        {
            // and 3 sharedrefs, which will be Disposed in 3 separate threads
            var p1 = SharedRef.Create(o1);
            var p2a = SharedRef.Create(o2);
            var p2b = SharedRef.Create(o2);
            t1 = Task.Run(() =>
            {
                using (p1)
                {
                    Console.WriteLine("{0}: in another thread, using o1", Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                    Console.WriteLine("{0}: in another thread, exiting scope", Thread.CurrentThread.ManagedThreadId);
                }
            });
            t2 = Task.Run(() =>
            {
                using (p2a)
                {
                    Console.WriteLine("{0}: in another thread, using o2", Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                    Console.WriteLine("{0}: in another thread, exiting scope", Thread.CurrentThread.ManagedThreadId);
                }
            });
            t3 = Task.Run(() =>
            {
                using (p2b)
                {
                    Console.WriteLine("{0}: in another thread, using o2", Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                    Console.WriteLine("{0}: in another thread, exiting scope", Thread.CurrentThread.ManagedThreadId);
                }
            });
            Console.WriteLine("{0}: exiting initial scope", Thread.CurrentThread.ManagedThreadId);
        }
        t1.Wait();
        t2.Wait();
        t3.Wait();
    }
}


No need. .NET has a garbage collector that will take care of cleaning up an object once nothing holds a reference to it.


Read this article for more info:

  • Understanding Garbage Collection in .NET


There are couple automatic memory management strategies in different platforms and programming languages.

Two major approaches are applicable to automatic memory management: reference counting and garbage collection. They are both worth examining, although the second one is by far the more powerful and generally applicable.

(Object-Oriented Software Construction by Bertran Meyer, p.301)

Reference counting (i.e. shared_ptr ) is one of the simplest way to achieve automatic memory management. It pretty simple but have some significant drawbacks (it can't deal with cyclic structures, and it has performance overhead in both time and space. For every operation on references the implementation will now execute an arithmetic operation — and, in the detachment case, a conditional instruction. In addition, every object must be extended with an extra field to hold the count).

The idea behind the first automatic memory management technique, reference counting, is simple. In every object, we keep a count of the number of references to the object; when this count becomes null, the object may be recycled. This solution is not hard to implement (at the language implementation level). We must update the reference count of any object in response to all operations that can create the object, attach a new reference to it and detach a reference from it.

(Object-Oriented Software Construction by Bertran Meyer, p.301)

Garbage collection (witch is used in CLR) is based on two main properties:

Soundness: every collected object is unreachable.

Completeness: every unreachable object will be collected.

Garbage collection basis

The basic algorithm usually includes two phases, at least conceptually: mark and sweep. The mark phase, starting from the origins, follows references recursively to traverse the active part of the structure, marking as reachable all the objects it encounters. The sweep phase traverses the whole memory structure, reclaiming unmarked elements and unmarking everything. As with reference counting, objects must include an extra field, used here for the marking; but the space overhead is negligible, since one bit suffices per object. As will be seen when we study dynamic binding, implementation of O-O facilities requires that every object carry some extra internal information (such as its type) in addition to its official fields corresponding to the attributes of the generating class. This information typically occupies one or two words per object; the marking bit can usually be squeezed into one of these extra words, so that in practice there is no observable overhead.

P.S. For more information about garbage collection in CLR see Chapter 20 "CLR via C#" by Jeffrey Richter

P.S.S. There is no equivalent for shared_ptr in .Net because .Net uses Garbage Collection for automatic memory management and if you want to use reference counting - you should implement it by hand (for example, for resource management).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜