C/C++ shared_ptr 的实现

  shared_ptr(共享指针)在STA(单线程)的场景并没有太多的意义,而且它还会浪费一些内存与处理器效能,它的价值并不是体现在STA环境下的,在STA场景内,一个对象在“函数调用链”之间的传递形式为“自上向下 (A -> B -> C)”,约定对象的释放的“诱点”始终是可靠的,我们可以在“调用函数链”拥有此对象最上层的函数进行释放。

  但是MTA(多线程)场景下按照STA场景下的释放形式就不行了,这是由多核心多线程的特性决定的,就好比我们在“协程(coroutine)”场景下管理一个对象安全的释放也很麻烦,你无法确定在何时可以安全的释放一个对象结构,那么在管理对象内存的过程中,我们作为一个“真实的人”是很容易出现遗漏,忘了释放的情况时有发生或者在错误的点进行了释放,而托管的世界里面只要GC(自动垃圾回收器)不出现类似的故障几乎不可能出现“遗忘释放的内存”,当然人为造成的托管泄漏那另当别论;由其是开发的系统越加庞大越来越复杂的时候。

  shared_ptr 采取一种 “自动的引用计数管理” 的方法来控制一个 “对象实例” 的安全释放,当然说是 “自动引用计数管理” 但主要是依靠来自 “C/C++” 编译器本身的特性完成的,我们知道C/C++对象若在 “函数栈上分配”的话,到该函数的 “}” 的位置,编译器会在build 时由编译器生成调用 “栈上对象” 的析构函数,而 shared_ptr 自动管理 “引用计数” 便是依靠的这种特性。

  当然有一些比较喜欢 “原汁原味” 的方式来管理对象的引用,这种就是 “手动引用计数管理” 它与 “自动的区别” 就是会麻烦很多当然 “自动引用计数管理”  还是有一些缺陷的,例:发生C/C++异常时是无法释放对象的,这是一种很容易被人忽略掉的一个问题,当然即便处理了异常也需要看具体的场景,有些情况下处理了异常并不会影响到对象的安全释放,但是另一类情况恐怕就很难说了,所以某些 “shared_ptr” 的设计者(designer)同时提供了 “手动引用计数管理” 的两组公共的API接口(::AddRef、::Release),但设计者通常并不建议 “开发人员” 利用这些API,它可能会带来一些不必要的麻烦问题。

  “引用计数管理” 在某些不正确的实现的中,是定义了类似如一个相关的 “public 基类”,在基类中设定了包含引用计数的功能数据字段、例:

class ObjectRef : 
	public nvm::Object,
	public nvm::IDisposable
{
private:
	nvm::Int32 m_nRefCount;
	nvm::Boolean m_disposed;

public:
        ObjectRef() {
          this->m_nRefCount = 0;
          this->m_disposed = false;
        }
	void AddRef() {
		nvm::threading::Interlocked::Increment(m_nRefCount);
	}
	void Release() {
		if (nvm::threading::Interlocked::Decrement(m_nRefCount) <= 0) {
			nvm::Boolean free_ = false;
			if (!this->m_disposed) {
				free_ = true;
				this->m_disposed = true;
			}
			if (free_) {
				this->Dispose();
			}
		}
	}
	virtual void Dispose() override {
		// TO:DO 在此处释放对象持有的资源
	}
};

  上述代码看上去似乎没有问题,的确它可以保证对象的安全释放,但是它不允许对象被 “深度拷贝”,否则它所管理的引用计数会出现问题,上述方法只建议 “手动管理引用计数”,另一方面类的成员函数的确可以调用 “delete” 释放 this 拥有的内存,但这是很危险的,外部不可控制的因素是很多的,同时这也违背了OO的一些思想,想一想你自己怎么干掉你自己,而且从权限角度来说它也没有这种资格。

  显然我们需要寻求一种更加友好的办法来管理 “引用计数”,显然 “shared_ptr” 很适合这类的情况,当然 “shared_ptr” 的实现也有与上面类似的,引用计数的管理是手动的方法。

class Foo :
	public nvm::Object
{
public:
	inline void Say()
	{
		printf("Hello world!");
	}
	inline virtual nvm::Int32 GetTypeCode() override
	{
		return typeid(Foo).hash_code();
	}
};

template<typename T>
class ObjectRef : 
	public nvm::Object,
	public nvm::IDisposable
{
private:
	nvm::Int32 m_nRefCount;
	nvm::Boolean m_disposed;
	const T* m_self;

public:
	ObjectRef(const T* ptr)
	{
		if (ptr == NULL)
		{
			throw new nvm::ArgumentNullException("ptr");
		}
		this->m_self = ptr;
	}
	inline void AddRef()
	{
		nvm::threading::Interlocked::Increment(m_nRefCount);
	}
	inline void Release()
	{
		if (nvm::threading::Interlocked::Decrement(m_nRefCount) <= 0)
		{
			nvm::Boolean free_ = false;
			if (!this->m_disposed)
			{
				free_ = true;
				this->m_disposed = true;
			}
			if (free_)
			{
				this->Dispose();
			}
		}
	}
	inline T* const operator->()
	{
		return (T*)this->m_self;
	}
	inline virtual void Dispose() override
	{
		// TO:DO 在此处释放对象持有的资源
	}
};

int __cdecl main(int argc, char* argv[])
{
	ObjectRef<Foo>* r = new ObjectRef<Foo>(new Foo());
	(*r)->Say();
	return getchar();
}

  上面的办法的确足够优秀,但是有没有更加便捷友好的方法呢?手动管理只要按照既定的要求的确不会出现故障,但是能否自动的管理计数? 答案是肯定的,前面提到了利用C/C++编译器的特性来完成这件事情,但它与上面的效率会有一些差距,主要是在多次深度内存拷贝的问题上面浪费掉了,但有时候我们并不应该仅仅只是站在效率的角度上思考这个问题,用效率换取更少的代码量而更少的代码量也意味着更少的BUG,这本来就是一种双面刃的东西,如何平衡本来就是件麻烦事情儿,在不同场景的应用中考虑的方式各有不同,但是考虑到场景足够的通用的情况下,这种方法或许是最佳的选择。

  当然一旦我们需要利用来自C/C++编译器本身的特性,那么自然我必须具有一个包含管理引用计数通用的 Intern Ref Table,由它托管各个对象之间的引用,虽然好处显然易见,但是坏处也不并不是没有,它需要付出不少的CPU效能(CPU原子态操作的损耗、全局竞争资源锁的消耗、Dictionary Add / Get 处理的消耗,额外的内存资源损耗)但与它所带来的价值来说并不一定就不值得,至少我们立于 “基础设施层架构” 的角度上来看,它是值得的。

class Foo :
	public nvm::Object
{
public:
	Foo()
	{

	}
	~Foo()
	{
		printf("Finalize self object\n");
	}
	inline void Say()
	{
		printf("Hello world!\n");
	}
	inline virtual nvm::Int32 GetTypeCode() override
	{
		return typeid(Foo).hash_code();
	}
};

int __cdecl main(int argc, char* argv[])
{
	nvm::SharedPtr<Foo> r = new Foo();
	r->Say();
	return getchar();
}

   上述是关乎 “nvm::SharedPtr” 的一个应用例子,当执行到 main 函数的 “}” 处时会自动的释放 Foo 的实例(前提是可以释放,但上述代码是必定释放的),另外 “sharedptr” 的源代码来源自 “nvm”(暂未开源),当然本人更希望各位读者能够自行好好参悟,然后自行进行实现,这个东西非常的基础但是往往越是基础的东西,越不简单,越是充满了无数前辈的智慧与结晶,它并不是看上去的那么普通、不出奇。

   sharedptr.h

#ifndef SHAREDPTR_H
#define SHAREDPTR_H

#include "nvm/object.h"
#include "nvm/exception.h"
#include "nvm/array.h"
#include "nvm/IDisposable.h"
#include "nvm/threading/Interlocked.h"
#include "nvm/threading/SpinLock.h"
#include "nvm/collections/Dictionary.h"
#include "nvm/utils/minirtm.h"

namespace nvm
{
	class SharedPtrRef sealed : 
		public Object
	{
	private:
		nvm::threading::SpinLock m_syncobj;
		nvm::collections::Dictionary<const void*, nvm::Int32> m_tables;
		static SharedPtrRef* m_defaultRef;

		template<typename T>
		friend class SharedPtr;
	protected:
		nvm::Int32 AddRef(const void* s);
		nvm::Int32 Release(const void* s);
		nvm::Int32 GetRefCount(const void* s);
		static SharedPtrRef& GetDefault();
		inline virtual nvm::Int32 GetTypeCode() override
		{
			return typeid(SharedPtrRef).hash_code();
		}
	};

	template<typename T>
	class SharedPtr : 
		public Object,
		public IDisposable
	{
	private:
		const void* m_self;
		nvm::Int32 m_type;

		SharedPtr(const void* ptr, nvm::Int32 type) throw()
		{
			if (ptr == NULL)
			{
				this->m_self = NULL;
				this->m_type = -1;
			}
			else
			{
				if (type < 0 || type >1)
				{
					throw new NotSupportedException("type");
				}
				this->m_self = ptr;
				this->m_type = type;
				this->AddRef();
			}
		}
	protected:
		inline virtual T* GetSelfPtr() throw()
		{
			if (this->m_self == NULL)
			{
				throw new InvalidOperationException("this");
			}
			if (this->m_type == 0)
			{
				return (T*)this->m_self;
			}
			else if (this->m_type == 1)
			{
				nvm::Array<T>* array_ = (nvm::Array<T>*)this->m_self;
				return array_->GetBuffer();
			}
			else
			{
				throw new NotSupportedException("type");
			}
		}
	public:
		inline nvm::Int32 AddRef()
		{
			return SharedPtrRef::GetDefault().AddRef(this->m_self);
		}
		inline nvm::Int32 Release()
		{
			return SharedPtrRef::GetDefault().Release(this->m_self);
		}
		SharedPtr()
		{
			this->m_self = NULL;
			this->m_type = -1;
		};
		SharedPtr(const SharedPtr<T>& ptr)
		{
			if (SharedPtr::IsNull(ptr))
			{
				this->m_self = NULL;
				this->m_type = -1;
			}
			else
			{
				SharedPtr<T>& sp = (SharedPtr<T>&)ptr;
				this->m_self = sp.m_self;
				this->m_type = sp.m_type;
				this->AddRef();
			}
		}
		SharedPtr(const T* ptr) : SharedPtr(ptr, 0)
		{

		}
		SharedPtr(const nvm::Array<T>* array_) : SharedPtr(array_, 1)
		{
			
		}
		inline static bool IsNull(const SharedPtr<T>& ptr)
		{
			SharedPtr<T>& sp = (SharedPtr<T>&)ptr;
			return nvm::utils::MiniRTM::IsNull(sp) || (sp.m_self == NULL);
		}
		virtual ~SharedPtr()
		{
			this->Dispose();
		}
		inline nvm::Int32 GetRefCount()
		{
			if (this->m_self == NULL)
			{
				return 0;
			}
			return SharedPtrRef::GetDefault().GetRefCount();
		}
		inline T* const operator->() throw()
		{
			return this->GetSelfPtr();
		}
		inline SharedPtr<T>& operator=(const SharedPtr<T>& right)
		{
			this->Dispose();
			if (SharedPtr::IsNull(right))
			{
				this->m_self = NULL;
				this->m_type = -1;
			}
			else
			{
				SharedPtr<T>& sp = (SharedPtr<T>&)right;
				this->m_self = sp.m_self;
				this->m_type = sp.m_type;
				this->AddRef();
			}
			return this->Clone();
		}
		inline SharedPtr<T>& Clone()
		{
			return *this;
		}
		inline virtual nvm::Int32 GetTypeCode() override
		{
			return typeid(SharedPtr<T>).hash_code();
		}
		inline virtual const type_info& GetTypeInfo()
		{
			return typeid(T);
		}
		inline virtual nvm::UInt32 GetHashCode() override
		{
			if (this->m_self == NULL)
			{
				return 0;
			}
			return nvm::utils::MiniRTM::GetHashCode(this->GetSelfPtr());
		}
		inline virtual void Dispose() override
		{
			if (this->m_self != NULL && this->Release() == 0)
			{
				if (this->m_type == 0)
				{
					delete ((T*)this->m_self);
				}
				else if (this->m_type == 1)
				{
					delete ((nvm::Array<T>*)this->m_self);
				}
				else
				{
					throw new NotSupportedException("type");
				}
				this->m_self = NULL;
				this->m_type = -1;
			}
		}
		inline virtual nvm::Boolean Equals(nvm::Object* o) override
		{
			const void* x = (const void*)this;
			const void* y = (const void*)o;
			if (x == y)
			{
				return true;
			}
			if (x == NULL || y == NULL)
			{
				return false;
			}
			if (this->GetTypeCode() != o->GetTypeCode())
			{
				return false;
			}
			nvm::SharedPtr<T>* right = (nvm::SharedPtr<T>*)o;
			return this->m_self == right->m_self;
		}
		inline virtual nvm::Boolean operator==(const nvm::SharedPtr<T>& right)
		{
			nvm::Boolean lnull = nvm::SharedPtr<T>::IsNull(*this);
			nvm::Boolean rnull = nvm::SharedPtr<T>::IsNull(right);
			if (lnull && rnull)
			{
				return true;
			}
			else if (lnull || rnull)
			{
				return false;
			}
			nvm::SharedPtr<T>& right_ = (nvm::SharedPtr<T>&)right;
			return this->Equals(&right_);
		}
		inline virtual nvm::Boolean operator!=(const nvm::SharedPtr<T>& right)
		{
			return !(*this == right);
		}
	};
};
#endif

   sharedptr.cpp

#include "nvm/sharedptr.h"

nvm::SharedPtrRef* nvm::SharedPtrRef::m_defaultRef = new nvm::SharedPtrRef;

nvm::Int32 nvm::SharedPtrRef::AddRef(const void* s)
{
	nvm::Int32 count_ = -1;
	if (s != NULL)
	{
		nvm::Boolean localTaken = false;
		this->m_syncobj.Enter(localTaken);
		if (localTaken)
		{
			if (!this->m_tables.ContainsKey(s))
			{
				this->m_tables.Add(s, 0);
			}
			count_ = nvm::threading::Interlocked::Increment(this->m_tables[s]);
			this->m_syncobj.Exit();
		}
	}
	return count_;
}

nvm::Int32 nvm::SharedPtrRef::Release(const void* s)
{
	nvm::Int32 count_ = -1;
	if (s != NULL)
	{
		nvm::Boolean localTaken = false;
		this->m_syncobj.Enter(localTaken);
		if (localTaken)
		{
			if (this->m_tables.ContainsKey(s))
			{
				count_ = nvm::threading::Interlocked::Decrement(m_tables[s]);
				if (count_ <= 0)
				{
					this->m_tables.Remove(s);
				}
			}
			this->m_syncobj.Exit();
		}
	}
	return count_;
}

nvm::Int32 nvm::SharedPtrRef::GetRefCount(const void* s)
{
	if (s == NULL)
	{
		return false;
	}
	nvm::Boolean localTaken = false;
	this->m_syncobj.Enter(localTaken);
	nvm::Int32 count_ = -1;
	if (localTaken)
	{
		if (this->m_tables.ContainsKey(s))
		{
			nvm::Int32& internval = m_tables[s];
			count_ = nvm::threading::Interlocked::Read(internval);
		}
		this->m_syncobj.Exit();
	}
	return count_;
}

nvm::SharedPtrRef& nvm::SharedPtrRef::GetDefault()
{
	return *m_defaultRef;
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中的 `shared_ptr` 是一个智能指针,用于管理动态分配的对象。它是 C++11 引入的一个特性,位于 `<memory>` 头文件中。 `shared_ptr` 可以用来共享一个对象的所有权。它会记录有多少个 `shared_ptr` 共享同一个对象,并且当所有的 `shared_ptr` 都不再使用该对象时,会自动释放对象的内存。 使用 `shared_ptr` 的语法如下所示: ```cpp #include <memory> int main() { // 创建 shared_ptr std::shared_ptr<int> ptr1 = std::make_shared<int>(42); // 共享对象的所有权 std::shared_ptr<int> ptr2 = ptr1; // 使用 shared_ptr 操作对象 *ptr1 = 100; std::cout << *ptr2 << std::endl; // 输出:100 // 释放对象内存 ptr1.reset(); std::cout << *ptr2 << std::endl; // 输出:100 return 0; } ``` 在上述示例中,我们首先创建了一个 `shared_ptr`,并使用 `make_shared` 函数动态分配一个整数对象,并将其初始化为 42。然后我们创建了另一个 `shared_ptr`,并将其指向第一个 `shared_ptr` 所管理的对象。通过这种方式,两个 `shared_ptr` 共享同一个整数对象。我们可以通过任意一个 `shared_ptr` 来访问和操作该对象,并且当两个 `shared_ptr` 都不再使用该对象时,会自动释放对象的内存。 需要注意的是,`shared_ptr` 使用引用计数的方式来管理对象的生命周期。当引用计数变为零时,即没有任何 `shared_ptr` 指向该对象时,会自动调用对象的析构函数来销毁对象。这种智能指针可以避免常见的内存泄漏问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值