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”(暂未开源),当然本人更希望各位读者能够自行好好参悟,然后自行进行实现,这个东西非常的基础但是往往越是基础的东西,越不简单,越是充满了无数前辈的智慧与结晶,它并不是看上去的那么普通、不出奇。
#ifndef SHAREDPTR_H
#define SHAREDPTR_H
#include "nvm/"
#include "nvm/"
#include "nvm/"
#include "nvm/"
#include "nvm/threading/"
#include "nvm/threading/"
#include "nvm/collections/"
#include "nvm/utils/"
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
#include "nvm/"
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;
}