Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

473 lines
16 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// This is a multithread-safe version of shared_ptr_mt.
// For basic documentation, see shared_ptr_mt.
///////////////////////////////////////////////////////////////////////////////
#ifndef EATHREAD_SHARED_PTR_MT_H
#define EATHREAD_SHARED_PTR_MT_H
#ifndef INCLUDED_eabase_H
#include <EABase/eabase.h>
#endif
#ifndef EATHREAD_EATHREAD_FUTEX_H
#include <eathread/eathread_futex.h>
#endif
#ifndef EATHREAD_EATHREAD_ATOMIC_H
#include <eathread/eathread_atomic.h>
#endif
// #include <memory> Temporarily disabled while we wait for compilers to modernize. // Declaration of std::auto_ptr.
#if defined(EA_PRAGMA_ONCE_SUPPORTED)
#pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result.
#endif
/// namespace EA
/// The standard Electronic Arts namespace
namespace EA
{
namespace Thread
{
/// class shared_ptr_mt
/// @brief Implements a thread-safe version of shared_ptr.
template<class T>
class shared_ptr_mt
{
private:
/// this_type
/// This is an alias for shared_ptr_mt<T>, this class.
typedef shared_ptr_mt<T> this_type;
/// reference_count_type
/// An internal reference count type. Must be convertable to int
/// so that the public use_count function can work.
typedef EA::Thread::AtomicInt32 reference_count_type;
T* mpValue; /// The owned pointer.
reference_count_type* mpRefCount; /// Reference count for owned pointer.
mutable Futex mMutex; /// Mutex guarding access to this class.
public:
typedef T element_type;
typedef T value_type;
/// shared_ptr_mt
/// Takes ownership of the pointer and sets the reference count
/// to the pointer to 1. It is OK if the input pointer is null.
/// The shared reference count is allocated on the heap via operator new.
/// If an exception occurs during the allocation of the shared
/// reference count, the owned pointer is deleted and the exception
/// is rethrown. A null pointer is given a reference count of 1.
explicit shared_ptr_mt(T* pValue = 0)
: mpValue(pValue), mMutex()
{
// We don't lock our mutex in this function, as this is the constructor
// and we assume that construction is already done in a thread-safe way
// by the owner of this object.
#if defined(EA_COMPILER_NO_EXCEPTIONS) || defined(EA_COMPILER_NO_UNWIND)
mpRefCount = new reference_count_type(1);
#else
EA_DISABLE_VC_WARNING(4571)
try
{
mpRefCount = new reference_count_type(1);
}
catch(...)
{
delete pValue;
//mpRefCount = 0; shouldn't be necessary.
throw;
}
EA_RESTORE_VC_WARNING()
#endif
}
/// shared_ptr_mt
/// Shares ownership of a pointer with another instance of shared_ptr_mt.
/// This function increments the shared reference count on the pointer.
shared_ptr_mt(shared_ptr_mt const& sharedPtr)
: mMutex()
{
// We don't lock our mutex in this function, as this is the constructor
// and we assume that construction is already done in a thread-safe way
// by the owner of this object.
sharedPtr.lock();
mpValue = sharedPtr.mpValue;
mpRefCount = sharedPtr.mpRefCount;
mpRefCount->Increment(); // Atomic operation
sharedPtr.unlock();
}
// Temporarily disabled while we wait for compilers to modernize.
//
// shared_ptr_mt
// Constructs a shared_ptr_mt from a std::auto_ptr. This class
// transfers ownership of the pointer from the auto_ptr by
// calling its release function.
// If an exception occurs during the allocation of the shared
// reference count, the owned pointer is deleted and the exception
// is rethrown.
//explicit shared_ptr_mt(std::auto_ptr<T>& autoPtr)
// : mMutex()
//{
// // We don't lock our mutex in this function, as this is the constructor
// // and we assume that construction is already done in a thread-safe way
// // by the owner of this object.
// mpValue = autoPtr.release();
//
// #if defined(EA_COMPILER_NO_EXCEPTIONS) || defined(EA_COMPILER_NO_UNWIND)
// mpRefCount = new reference_count_type(1);
// #else
// try
// {
// mpRefCount = new reference_count_type(1);
// }
// catch(...)
// {
// delete mpValue;
// mpValue = 0;
// //mpRefCount = 0; shouldn't be necessary.
// throw;
// }
// #endif
//}
/// ~shared_ptr_mt
/// Decrements the reference count for the owned pointer. If the
/// reference count goes to zero, the owned pointer is deleted and
/// the shared reference count is deleted.
~shared_ptr_mt()
{
lock();
const reference_count_type newRefCount(mpRefCount->Decrement()); // Atomic operation
// EAT_ASSERT(newRefCount >= 0);
if(newRefCount == 0)
{
// we should only be deleting the pointer if it is not null. It is possible that the
// user has created a shared ptr without passing in a value.
if (mpValue)
delete mpValue;
delete mpRefCount;
}
unlock();
}
/// operator=
/// Copies another shared_ptr_mt to this object. Note that this object
/// may already own a shared pointer with another different pointer
/// (but still of the same type) before this call. In that case,
/// this function releases the old pointer, decrementing its reference
/// count and deleting it if zero, takes shared ownership of the new
/// pointer and increments its reference count.
shared_ptr_mt& operator=(shared_ptr_mt const& sharedPtr)
{
// We don't lock mutexes here because we let the swap function
// below do the locking and assignment. The if statement below
// isn't protected within a lock operation because it wouldn't
// help by being so because if mpValue is changing during the
// the execution of this function then the user has an external
// race condition that needs to be managed at that level.
if(mpValue != sharedPtr.mpValue)
{
// The easiest thing to do is to create a temporary and
// copy ourselves ourselves into it. This is a standard
// method for switching pointer ownership in systems like this.
shared_ptr_mt(sharedPtr).swap(*this);
}
return *this;
}
// Temporarily disabled while we wait for compilers to modernize.
//
// operator=
// Transfers ownership of a std::auto_ptr to this class.
//shared_ptr_mt& operator=(std::auto_ptr<T>& autoPtr)
//{
// // We don't lock any mutexes here because we let the swap function do that.
// // EAT_ASSERT(mpValue != autoPtr.get());
// shared_ptr_mt(autoPtr).swap(*this);
// return *this;
//}
// operator=
// We do not defined this function in order to maintain compatibility
// with the currently proposed (2003) C++ standard addition. Use reset instead.
// shared_ptr_mt& operator=(T* pValue);
// {
// reset(pValue);
// return *this;
// }
/// lock
/// @brief Locks our mutex for thread-safe access.
/// It is a const function because const-ness refers to the underlying pointer being
/// held and not this class.
void lock() const
{
mMutex.Lock();
}
/// unlock
/// @brief Unlocks our mutex which was previous locked.
/// It is a const function because const-ness refers to the underlying pointer being
/// held and not this class.
void unlock() const
{
mMutex.Unlock();
}
/// reset
/// Releases the owned pointer and takes ownership of the
/// passed in pointer. If the passed in pointer is the same
/// as the owned pointer, nothing is done. The passed in pointer
/// can be null, in which case the use count is set to 1.
void reset(T* pValue = 0)
{
// We don't lock any mutexes here because we let the swap function do that.
// We don't lock for the 'if' statement below because that wouldn't really buy anything.
if(pValue != mpValue)
{
// The easiest thing to do is to create a temporary and
// copy ourselves ourselves into it. This is a standard
// method for switching pointer ownership in systems like this.
shared_ptr_mt(pValue).swap(*this);
}
}
/// swap
/// Exchanges the owned pointer beween two shared_ptr_mt objects.
void swap(shared_ptr_mt<T>& sharedPtr)
{
lock();
sharedPtr.lock();
// std::swap(mpValue, sharedPtr.mpValue); // Not used so that we can reduce a dependency.
T* const pValue = sharedPtr.mpValue;
sharedPtr.mpValue = mpValue;
mpValue = pValue;
// std::swap(mpRefCount, sharedPtr.mpRefCount); // Not used so that we can reduce a dependency.
reference_count_type* const pRefCount = sharedPtr.mpRefCount;
sharedPtr.mpRefCount = mpRefCount;
mpRefCount = pRefCount;
sharedPtr.unlock();
unlock();
}
/// operator*
/// Returns the owner pointer dereferenced.
/// Example usage:
/// shared_ptr_mt<int> ptr = new int(3);
/// int x = *ptr;
T& operator*() const
{
// We don't lock here because this is essentially a read operation.
// We don't put a SMP read barrier here because we assume the caller does such things.
// EAT_ASSERT(mpValue);
return *mpValue;
}
/// operator->
/// Allows access to the owned pointer via operator->()
/// Example usage:
/// struct X{ void DoSomething(); };
/// shared_ptr_mt<int> ptr = new X;
/// ptr->DoSomething();
T* operator->() const
{
// We don't lock here because this is essentially a read operation.
// We don't put a SMP read barrier here because we assume the caller does such things.
// EAT_ASSERT(mpValue);
return mpValue;
}
/// get
/// Returns the owned pointer. Note that this class does
/// not provide an operator T() function. This is because such
/// a thing (automatic conversion) is deemed unsafe.
/// Example usage:
/// struct X{ void DoSomething(); };
/// shared_ptr_mt<int> ptr = new X;
/// X* pX = ptr.get();
/// pX->DoSomething();
T* get() const
{
// We don't lock here because this is essentially a read operation.
// We don't put a SMP read barrier here because we assume the caller does such things.
return mpValue;
}
/// use_count
/// Returns the reference count on the owned pointer.
/// The return value is one if the owned pointer is null.
int use_count() const
{
// We don't lock here because this is essentially a read operation.
// We don't put a SMP read barrier here because we assume the caller does such things.
// EAT_ASSERT(mpRefCount);
return (int)*mpRefCount;
}
/// unique
/// Returns true if the reference count on the owned pointer is one.
/// The return value is true if the owned pointer is null.
bool unique() const
{
// We don't lock here because this is essentially a read operation.
// We don't put a SMP read barrier here because we assume the caller does such things.
// EAT_ASSERT(mpRefCount);
return (*mpRefCount == 1);
}
/// add_ref
/// Manually increments the reference count on the owned pointer.
/// This is currently disabled because it isn't in part of the
/// proposed C++ language addition.
/// int add_ref()
/// {
/// lock();
/// // EAT_ASSERT(mpRefCount);
/// ++*mpRefCount; // Atomic operation
/// unlock();
/// }
/// release_ref
/// Manually increments the reference count on the owned pointer.
/// If the reference count becomes zero, then the owned pointer
/// is deleted and reset(0) is called. For any given instance of
/// shared_ptr_mt, release_ref can only be called as many times as --
/// but no more than -- the number of times add_ref was called
/// for that same shared_ptr_mt. Otherwise, separate instances of
/// shared_ptr_mt would be left with dangling owned pointer instances.
/// This is currently disabled because it isn't in part of the
/// proposed C++ language addition.
/// int release_ref()
/// {
/// lock();
/// // EAT_ASSERT(mpRefCount);
/// if(*mpRefCount > 1){
/// const int nReturnValue = --*mpRefCount; // Atomic operation
/// unlock();
/// return nReturnValue;
/// }
/// reset(0);
/// unlock();
/// return 0;
/// }
/// Implicit operator bool
/// Allows for using a scoped_ptr as a boolean.
/// Example usage:
/// shared_ptr_mt<int> ptr = new int(3);
/// if(ptr)
/// ++*ptr;
///
/// Note that below we do not use operator bool(). The reason for this
/// is that booleans automatically convert up to short, int, float, etc.
/// The result is that this: if(scopedPtr == 1) would yield true (bad).
typedef T* (this_type::*bool_)() const;
operator bool_() const
{
// We don't lock here because this is essentially a read operation.
if(mpValue)
return &this_type::get;
return 0;
}
/// operator!
/// This returns the opposite of operator bool; it returns true if
/// the owned pointer is null. Some compilers require this and some don't.
/// shared_ptr_mt<int> ptr = new int(3);
/// if(!ptr)
/// EAT_ASSERT(false);
bool operator!() const
{
// We don't lock here because this is essentially a read operation.
return (mpValue == 0);
}
}; // class shared_ptr_mt
/// get_pointer
/// returns shared_ptr_mt::get() via the input shared_ptr_mt.
template<class T>
inline T* get_pointer(const shared_ptr_mt<T>& sharedPtr)
{
return sharedPtr.get();
}
/// swap
/// Exchanges the owned pointer beween two shared_ptr_mt objects.
/// This non-member version is useful for compatibility of shared_ptr_mt
/// objects with the C++ Standard Library and other libraries.
template<class T>
inline void swap(shared_ptr_mt<T>& sharedPtr1, shared_ptr_mt<T>& sharedPtr2)
{
sharedPtr1.swap(sharedPtr2);
}
/// operator!=
/// Compares two shared_ptr_mt objects for equality. Equality is defined as
/// being true when the pointer shared between two shared_ptr_mt objects is equal.
/// It is debatable what the appropriate definition of equality is between two
/// shared_ptr_mt objects, but we follow the current 2nd generation C++ standard proposal.
template<class T, class U>
inline bool operator==(const shared_ptr_mt<T>& sharedPtr1, const shared_ptr_mt<U>& sharedPtr2)
{
// EAT_ASSERT((sharedPtr1.get() != sharedPtr2.get()) || (sharedPtr1.use_count() == sharedPtr2.use_count()));
return (sharedPtr1.get() == sharedPtr2.get());
}
/// operator!=
/// Compares two shared_ptr_mt objects for inequality. Equality is defined as
/// being true when the pointer shared between two shared_ptr_mt objects is equal.
/// It is debatable what the appropriate definition of equality is between two
/// shared_ptr_mt objects, but we follow the current 2nd generation C++ standard proposal.
template<class T, class U>
inline bool operator!=(const shared_ptr_mt<T>& sharedPtr1, const shared_ptr_mt<U>& sharedPtr2)
{
// EAT_ASSERT((sharedPtr1.get() != sharedPtr2.get()) || (sharedPtr1.use_count() == sharedPtr2.use_count()));
return (sharedPtr1.get() != sharedPtr2.get());
}
/// operator<
/// Returns which shared_ptr_mt is 'less' than the other. Useful when storing
/// sorted containers of shared_ptr_mt objects.
template<class T, class U>
inline bool operator<(const shared_ptr_mt<T>& sharedPtr1, const shared_ptr_mt<U>& sharedPtr2)
{
return (sharedPtr1.get() < sharedPtr2.get()); // Alternatively use: std::less<T*>(a.get(), b.get());
}
} // namespace Thread
} // namespace EA
#endif // EATHREAD_SHARED_PTR_MT_H