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

It's just curious that I used boost: shared_ptr in the past LOT - so that multiple objects keep a common pointer to one object, etc.

Is there an equivalent of this function in C #?

+3
c #
source share
5 answers

boost :: shared_ptr allows you to reference counted pointers in an environment that is not garbage collected. The .NET runtime allows you to completely collect garbage, so there is no need for a container of link pointers - just save links to objects.

+9
source share

Not necessary. .NET has a garbage collector that takes care of clearing the object as soon as nothing references it.

+5
source share

GC eliminates the need for shared pointers when it comes to memory management, but it does not do this for resource management.

IDisposable is a .NET resource management solution, but it does not allow sharing semantics. See this article for a detailed discussion of weaknesses.

Here's a simple implementation of SharedRef that follows the boost :: shared_ptr pattern of the reference count object allocated by the heap. Not sure how useful it is, 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:

  • To provide only one reference counter for each shared object, make sure you only create 1 SharedRef directly from the object, and then create new SharedRefs from previous SharedRefs. This is the same for boost :: shared_ptr. It would be nice to add some protection for the class if you forget this.
  • It seems that SharedRef itself should be a reference type allocated on the heap, but I think this is the only way to make it disposable.
  • I have not yet implemented the equivalent of weak_ptr, but I think this could be easily added.
  • It is thread safe (I think), but it can probably be more efficient as it uses locks.

Here is an example test code showing it in action through 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(); } } 
+5
source share

Read this article for more information:

+1
source share

There are several strategies for automatic memory management on different platforms and programming languages.

Two main approaches are used for automatic memory management: link counting and garbage collection. They both deserve to be studied, although the second is certainly more powerful and generally applicable.

(Development of Object Oriented Software by Bertrand Meyer, p. 301)

Link counting (i.e. shared_ptr) is one of the easiest ways to provide automatic memory management. It's quite simple, but it has some significant drawbacks (it cannot deal with cyclic structures, and it has an overhead of performance both in time and in space. For each operation on links, the implementation now performs an arithmetic operation - and, in in case of disconnection, conditional instruction.In addition, each object should be expanded with an additional field for storing the counter).

The idea of ​​the first method of automatic memory management, link counting, is simple. In each object, we store the number of references to the object; when this count becomes zero, the facility can be recycled. This solution is not difficult to implement (at the language implementation level). We must update the reference counter of any object in response to all operations that the object can do, attach a new link to it and disconnect the link from it.

(Development of Object Oriented Software by Bertrand Meyer, p. 301)

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

Sound : each collected object is inaccessible.

Completeness : every unreachable object will be assembled.

Garbage Collection Base

The basic algorithm usually involves two phases, at least conceptually: sign and sweep. The label phase, starting from the source, recursively refers to the links to cross the active part of the structure, designating as reachable all the objects that it encounters. The sweep phase traverses the entire memory structure, restoring unlabeled elements and marking everything out. As with reference counting, objects must include an additional field used here for marking; but the service data of the space is insignificant, since one bit is enough for each object. As we will see, when we study dynamic binding, the implementation of OO objects requires that each object carry some additional internal information (for example, its type) in addition to its official fields corresponding to the attributes of the generating class. This information usually takes one or two words per object; The marking bit can usually be compressed into one of these additional words, so that in practice there is no noticeable overhead.

PS For more information on garbage collection in the CLR, see Chapter 20, β€œCLR via C #,” by Jeffrey Richter

PSS In .Net there is no equivalent for shared_ptr.Net. Uses the Garbage Collection for automatic memory management, and if you want to use reference counting, you must implement it manually (for example, to manage resources).

0
source share

All Articles