在C#中有一个boost :: shared_ptr <t>等价物吗?</t>

时间:2010-01-17 00:01:10

标签: c#

好奇,我一直在使用boost:shared_ptr过去很多 - 因为有多个对象存储一个指向单个对象的共享指针等。

C#中是否有相同的功能?

5 个答案:

答案 0 :(得分:9)

boost :: shared_ptr允许在非垃圾收集的环境中引用计数指针。 .NET运行时允许完全垃圾收集,因此不需要引用计数指针容器 - 只需存储对象引用。

答案 1 :(得分:5)

没有必要。 .NET有一个垃圾收集器,一旦没有任何对象的引用,它将负责清理对象。

答案 2 :(得分:5)

GC在内存管理方面不需要共享指针,但它不会对资源管理执行此操作。

IDisposable是.NET的资源管理解决方案,但它不允许共享所有权语义。有关弱点的详细讨论,请参见this article

这是SharedRef的一个简单实现,它遵循堆分配的引用计数对象的boost :: shared_ptr模式。 不确定它有多么有用,但随意评论和改进它......

/// <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;
        }
    }
}

注意:

  1. 要确保每个共享对象只有一个引用计数,请确保只直接从该对象创建1个SharedRef,然后从以前的SharedRefs创建新的SharedRef。对于boost :: shared_ptr,这是相同的。如果忘记这一点,最好为课程添加一些保护。
  2. 似乎很遗憾,SharedRef本身必须是一个在堆上分配的引用类型,但我认为这是使它成为Disposable的唯一方法。
  3. 我还没有实现相当于weak_ptr的功能,但我认为可以轻松添加。
  4. 它是线程安全的(我认为),但可能因为它使用锁而更有效。
  5. 这是一些测试代码,显示它在3个线程中的运行情况。

    [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();
        }
    }
    

答案 3 :(得分:1)

阅读这篇文章了解更多信息:

答案 4 :(得分:0)

在不同的平台和编程语言中有几种自动内存管理策略。

两种主要方法适用于自动内存管理:参考 计数和垃圾收集。它们都值得研究,尽管第二个 到目前为止,它更强大,更普遍适用。

(Bertran Meyer的面向对象软件构建,p.301)

引用计数(即shared_ptr)是实现自动内存管理的最简单方法之一。它非常简单但有一些明显的缺点(它不能处理循环结构,并且它在时间和空间上都有性能开销。对于引用的每个操作,实现现在将执行算术运算 - 并且,在分离的情况下,条件指令。此外,每个对象必须使用额外的字段进行扩展以保存计数。

第一种自动内存管理技术背后的思想,参考计数, 很简单。在每个对象中,我们保持对对象的引用数量的计数;什么时候 该计数变为空,该对象可以被回收。 该解决方案并不难实现(在语言实现级别)。我们 必须更新任何对象的引用计数以响应可以创建的所有操作 对象,附加一个新的引用并从中分离引用。

(Bertran Meyer的面向对象软件构建,p.301)

垃圾收集(在CLR中使用)基于两个主要属性:

健全性:每个收集的对象都无法访问。

完整性:将收集每个无法访问的对象。

垃圾收集基础

基本算法通常包括两个阶段,至少在概念上:标记和 扫。标记阶段,从起源开始,递归地跟随参考 遍历结构的活动部分,将其遇到的所有对象标记为可达。 扫描阶段遍历整个存储器结构,回收未标记的元素 一切都没有标记。 与引用计数一样,对象必须包含一个额外的字段,此处用于表示 标记;但是空间开销可以忽略不计,因为每个对象一个就足够了。正如我们研究动态绑定时所看到的,O-O设施的实现需要每一个 对象除了官方之外还携带一些额外的内部信息(例如其类型) 对应于生成类的属性的字段。这个信息通常 每个对象占一个或两个单词;标记位通常可以挤入其中一个 这些额外的词,所以在实践中没有可观察到的开销。

P.S。有关CLR中垃圾收集的更多信息,请参阅Jeffrey Richter的第20章“通过C#CLR”

P.S.S。 .Net中的shared_ptr没有等价物,因为.Net使用垃圾收集进行自动内存管理,如果你想使用引用计数 - 你应该手动实现它(例如,用于资源管理)。