From ddaa46867df89eef563413a41aa9bd529d7e93da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@nocternity.net>
Date: Wed, 2 Jan 2019 15:50:21 +0100
Subject: [PATCH] Reference counting helpers - Progress, tests

 * Assignment copy/move operators
 * Boolean conversion / ! operator
 * Content access
---
 include/ebcl/RefCount.hh        |  43 +++++-
 include/ebcl/inline/RefCount.hh | 155 +++++++++++++++++++--
 tests/ref-count.cc              | 233 ++++++++++++++++++++++++++++++++
 3 files changed, 419 insertions(+), 12 deletions(-)
 create mode 100644 tests/ref-count.cc

diff --git a/include/ebcl/RefCount.hh b/include/ebcl/RefCount.hh
index 9038baf..56dd892 100644
--- a/include/ebcl/RefCount.hh
+++ b/include/ebcl/RefCount.hh
@@ -41,6 +41,9 @@ class T_ReferenceCountedClass
 
     protected:
 	T_ReferenceCountedClass( ) noexcept;
+
+    public:
+	uint32_t getReferenceCount( ) const noexcept;
 };
 
 // Shortcuts
@@ -72,6 +75,7 @@ class T_RcPtr
 
     private:
 	T_Target* target_;
+	void setTarget_( T_Target* t ) noexcept;
 
     public:
 
@@ -80,27 +84,58 @@ class T_RcPtr
 	T_RcPtr( ) noexcept;
 	~T_RcPtr( ) noexcept;
 
+	// Constructor function
+	template< typename... AT >
+		static T_RcPtr< Type > New( AT&& ... arguments );
+
 	void clear( ) noexcept;
 
 	// ---------------------------------------------------------------------
-	// Copy constructors
+	// Copy constructors and operators
 
 	T_RcPtr( T_Self const& other ) noexcept;
+	T_Self& operator= ( T_Self const& other ) noexcept;
+
 	template< typename Q , T_EnableForChild< Type , Q > = true >
 		T_RcPtr( T_RcPtr< Q > const& source ) noexcept;
 	template< typename Q , T_EnableForParent< Type , Q > = false >
 		explicit T_RcPtr( T_RcPtr< Q > const& source );
 
+	template< typename Q , T_EnableForChild< Type , Q > = true >
+		T_Self& operator= ( T_RcPtr< Q > const& other ) noexcept;
+	template< typename Q , T_EnableForParent< Type , Q > = false >
+		T_Self& operator= ( T_RcPtr< Q > const& other );
+
 	// ---------------------------------------------------------------------
-	// Move constructors
+	// Move constructors and operators
 
 	template< typename Q , T_EnableForChild< Type , Q > = true >
 		T_RcPtr( T_RcPtr< Q >&& other ) noexcept;
-
 	template< typename Q , T_EnableForParent< Type , Q > = false >
 		explicit T_RcPtr( T_RcPtr< Q >&& other );
-};
 
+	template< typename Q , T_EnableForChild< Type , Q > = true >
+		T_Self& operator= ( T_RcPtr< Q >&& other ) noexcept;
+	template< typename Q , T_EnableForParent< Type , Q > = false >
+		T_Self& operator= ( T_RcPtr< Q >&& other );
+
+	// ---------------------------------------------------------------------
+	// Boolean conversion
+
+	operator bool ( ) const noexcept;
+	bool operator! ( ) const noexcept;
+
+	// ---------------------------------------------------------------------
+	// Content access
+
+	// Access the actual pointer
+	Type * get( ) const noexcept;
+	explicit operator Type* ( ) const noexcept;
+
+	// Access the object; undefined behavior if get() == nullptr.
+	Type* operator -> ( ) const;
+	Type& operator * ( ) const;
+};
 
 
 } // namespace
diff --git a/include/ebcl/inline/RefCount.hh b/include/ebcl/inline/RefCount.hh
index 7850652..97fbd8e 100644
--- a/include/ebcl/inline/RefCount.hh
+++ b/include/ebcl/inline/RefCount.hh
@@ -27,6 +27,12 @@ inline bool T_ReferenceCountedClass< B >::decreaseReferences_( ) noexcept
 	return ( -- referenceCounter_ ) == 0;
 }
 
+template< bool B >
+inline uint32_t T_ReferenceCountedClass< B >::getReferenceCount( ) const noexcept
+{
+	return uint32_t{ referenceCounter_ };
+}
+
 
 /*= T_RcPtr ==================================================================*/
 
@@ -41,6 +47,30 @@ inline T_RcPtr< T >::~T_RcPtr( ) noexcept
 	clear( );
 }
 
+template< typename T >
+template< typename... AT >
+inline T_RcPtr< T > T_RcPtr< T >::New(
+		AT&& ... arguments )
+{
+	T* const obj{ new T( std::forward< AT >( arguments ) ... ) };
+	T_RcPtr< T > ptr;
+	ptr.setTarget_( obj );
+	return ptr;
+}
+
+/*----------------------------------------------------------------------------*/
+
+template< typename T >
+inline void T_RcPtr< T >::setTarget_(
+		T_Target* t ) noexcept
+{
+	assert( target_ == nullptr );
+	if ( t ) {
+		t->increaseReferences_( );
+	}
+	target_ = t;
+}
+
 template< typename T >
 inline void T_RcPtr< T >::clear( ) noexcept
 {
@@ -55,11 +85,9 @@ inline void T_RcPtr< T >::clear( ) noexcept
 template< typename T >
 inline T_RcPtr< T >::T_RcPtr(
 		T_RcPtr< T > const& other ) noexcept
-	: target_{ other.target_ }
+	: target_{ nullptr }
 {
-	if ( target_ ) {
-		target_->increaseReferences_( );
-	}
+	setTarget_( other.target_ );
 }
 
 template< typename T >
@@ -68,11 +96,9 @@ template<
 	T_EnableForChild< T , Q >
 > inline T_RcPtr< T >::T_RcPtr(
 		T_RcPtr< Q > const& other ) noexcept
-	: target_{ other.target_ }
+	: target_{ nullptr }
 {
-	if ( target_ ) {
-		target_->increaseReferences_( );
-	}
+	setTarget_( other.target_ );
 }
 
 template< typename T >
@@ -93,6 +119,45 @@ template<
 	}
 }
 
+template< typename T >
+inline T_RcPtr< T >& T_RcPtr< T >::operator= (
+		T_Self const& other ) noexcept
+{
+	clear( );
+	setTarget_( other.target_ );
+	return *this;
+}
+
+template< typename T >
+template<
+	typename Q ,
+	T_EnableForChild< T , Q >
+> inline T_RcPtr< T >& T_RcPtr< T >::operator= (
+		T_RcPtr< Q > const& other ) noexcept
+{
+	clear( );
+	setTarget_( other.target_ );
+	return *this;
+}
+
+template< typename T >
+template<
+	typename Q ,
+	T_EnableForParent< T , Q >
+> inline T_RcPtr< T >& T_RcPtr< T >::operator= (
+		T_RcPtr< Q > const& other )
+{
+	clear( );
+	if ( other.target_ ) {
+		T* const nt{ dynamic_cast< T* >( other.target_ ) };
+		if ( nt == nullptr ) {
+			throw std::bad_cast( );
+		}
+		setTarget_( nt );
+	}
+	return *this;
+}
+
 /*----------------------------------------------------------------------------*/
 
 template< typename T >
@@ -124,6 +189,80 @@ template<
 	}
 }
 
+template< typename T >
+template<
+	typename Q ,
+	T_EnableForChild< T , Q >
+> inline T_RcPtr< T >& T_RcPtr< T >::operator= (
+		T_RcPtr< Q >&& other ) noexcept
+{
+	clear( );
+	target_ = other.target_;
+	other.target_ = nullptr;
+	return *this;
+}
+
+template< typename T >
+template<
+	typename Q ,
+	T_EnableForParent< T , Q >
+> inline T_RcPtr< T >& T_RcPtr< T >::operator= (
+		T_RcPtr< Q >&& other )
+{
+	clear( );
+	if ( other.target_ ) {
+		T* const nt{ dynamic_cast< T* >( other.target_ ) };
+		if ( nt == nullptr ) {
+			throw std::bad_cast( );
+		}
+		other.target_ = nullptr;
+		target_ = nt;
+	}
+	return *this;
+}
+
+/*----------------------------------------------------------------------------*/
+
+template< typename T >
+inline T_RcPtr< T >::operator bool ( ) const noexcept
+{
+	return target_ != nullptr;
+}
+
+template< typename T >
+inline bool T_RcPtr< T >::operator! ( ) const noexcept
+{
+	return target_ == nullptr;
+}
+
+/*----------------------------------------------------------------------------*/
+
+template< typename T >
+inline T_RcPtr< T >::operator T* ( ) const noexcept
+{
+	return target_;
+}
+
+template< typename T >
+inline T* T_RcPtr< T >::get( ) const noexcept
+{
+	return target_;
+}
+
+template< typename T >
+inline T* T_RcPtr< T >::operator -> ( ) const
+{
+	assert( target_ != nullptr );
+	return target_;
+}
+
+template< typename T >
+inline T& T_RcPtr< T >::operator * ( ) const
+{
+	assert( target_ != nullptr );
+	return *target_;
+}
+
 
 } // namespace ebcl
 #endif // _H_EBCL_INLINE_REFCOUNT
diff --git a/tests/ref-count.cc b/tests/ref-count.cc
new file mode 100644
index 0000000..186274d
--- /dev/null
+++ b/tests/ref-count.cc
@@ -0,0 +1,233 @@
+#include <ebcl/RefCount.hh>
+#include <cppunit/extensions/HelperMacros.h>
+using namespace ebcl;
+
+class RefCountTest : public CppUnit::TestFixture
+{
+	CPPUNIT_TEST_SUITE( RefCountTest );
+		CPPUNIT_TEST( testStackInstance );
+
+		CPPUNIT_TEST( testEmpty );
+		CPPUNIT_TEST( testConstruct );
+
+		CPPUNIT_TEST( testCopyCons );
+		CPPUNIT_TEST( testCopyAss );
+		CPPUNIT_TEST( testMoveCons );
+		CPPUNIT_TEST( testMoveAss );
+	CPPUNIT_TEST_SUITE_END( );
+
+    public:
+	void setUp( ) override;
+
+	void testStackInstance( );
+
+	void testEmpty( );
+	void testConstruct( );
+
+	void testCopyCons( );
+	void testCopyAss( );
+	void testMoveCons( );
+	void testMoveAss( );
+};
+CPPUNIT_TEST_SUITE_REGISTRATION( RefCountTest );
+
+/*----------------------------------------------------------------------------*/
+
+namespace {
+
+#define M_MKTESTDECL_( CLS , ATOMIC ) \
+	class T_ ##CLS : public T_ReferenceCountedClass< ATOMIC > \
+	{ \
+	    public: \
+		static uint32_t cNew; \
+		static uint32_t cDel; \
+		T_ ##CLS( ) noexcept { cNew ++; } \
+		~T_ ##CLS( ) noexcept { cDel ++; } \
+	}; \
+	uint32_t T_ ##CLS::cNew = 0; \
+	uint32_t T_ ##CLS::cDel = 0; \
+	using P_ ##CLS = T_RcPtr< T_ ##CLS >
+
+M_MKTESTDECL_( Atomic , true );
+M_MKTESTDECL_( Simple , false );
+
+class T_Child : public T_Simple { };
+using P_Child = T_RcPtr< T_Child >;
+
+} // namespace
+
+/*----------------------------------------------------------------------------*/
+
+void RefCountTest::setUp( )
+{
+	T_Atomic::cNew = T_Atomic::cDel = 0;
+	T_Simple::cNew = T_Simple::cDel = 0;
+}
+
+/*----------------------------------------------------------------------------*/
+
+void RefCountTest::testStackInstance( )
+{
+	{
+		T_Atomic t1;
+		T_Simple t2;
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cNew );
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cNew );
+		CPPUNIT_ASSERT_EQUAL( 0u , t1.getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 0u , t2.getReferenceCount( ) );
+	}
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cDel );
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cDel );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void RefCountTest::testEmpty( )
+{
+	{
+		P_Atomic p1;
+		P_Simple p2;
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Atomic::cNew );
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Simple::cNew );
+		CPPUNIT_ASSERT_EQUAL( false , bool{ p1 } );
+		CPPUNIT_ASSERT_EQUAL( false , bool{ p2 } );
+		CPPUNIT_ASSERT_EQUAL( true , !p1 );
+		CPPUNIT_ASSERT_EQUAL( true , !p2 );
+		CPPUNIT_ASSERT_EQUAL( (T_Atomic*) nullptr , p1.get( ) );
+		CPPUNIT_ASSERT_EQUAL( (T_Simple*) nullptr , p2.get( ) );
+		CPPUNIT_ASSERT_EQUAL( (T_Atomic*) nullptr , (T_Atomic*) p1 );
+		CPPUNIT_ASSERT_EQUAL( (T_Simple*) nullptr , (T_Simple*) p2 );
+	}
+	CPPUNIT_ASSERT_EQUAL( 0u , T_Atomic::cDel );
+	CPPUNIT_ASSERT_EQUAL( 0u , T_Simple::cDel );
+}
+
+void RefCountTest::testConstruct( )
+{
+	{
+		P_Atomic p1{ P_Atomic::New( ) };
+		P_Simple p2{ P_Simple::New( ) };
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cNew );
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cNew );
+		CPPUNIT_ASSERT_EQUAL( true , bool{ p1 } );
+		CPPUNIT_ASSERT_EQUAL( true , bool{ p2 } );
+		CPPUNIT_ASSERT_EQUAL( false , !p1 );
+		CPPUNIT_ASSERT_EQUAL( false , !p2 );
+		CPPUNIT_ASSERT( (T_Atomic*) nullptr != p1.get( ) );
+		CPPUNIT_ASSERT( (T_Simple*) nullptr != p2.get( ) );
+		CPPUNIT_ASSERT( (T_Atomic*) nullptr != (T_Atomic*) p1 );
+		CPPUNIT_ASSERT( (T_Simple*) nullptr != (T_Simple*) p2 );
+		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 1u , (*p1).getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 1u , (*p2).getReferenceCount( ) );
+	}
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cDel );
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cDel );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void RefCountTest::testCopyCons( )
+{
+	{
+		P_Atomic p1{ P_Atomic::New( ) };
+		P_Simple p2{ P_Simple::New( ) };
+		{
+			P_Atomic c1{ p1 };
+			P_Simple c2{ p2 };
+			CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cNew );
+			CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cNew );
+			CPPUNIT_ASSERT( c1 );
+			CPPUNIT_ASSERT( c2 );
+			CPPUNIT_ASSERT( p1.get( ) == c1.get( ) );
+			CPPUNIT_ASSERT( p2.get( ) == c2.get( ) );
+			CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
+			CPPUNIT_ASSERT_EQUAL( 2u , p2->getReferenceCount( ) );
+		}
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Atomic::cDel );
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Simple::cDel );
+		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );
+	}
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cDel );
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cDel );
+}
+
+void RefCountTest::testCopyAss( )
+{
+	{
+		P_Atomic p1{ P_Atomic::New( ) };
+		P_Simple p2{ P_Simple::New( ) };
+		{
+			P_Atomic c1;
+			P_Simple c2;
+			CPPUNIT_ASSERT( !c1 );
+			CPPUNIT_ASSERT( !c2 );
+			c1 = p1;
+			c2 = p2;
+			CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cNew );
+			CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cNew );
+			CPPUNIT_ASSERT( c1 );
+			CPPUNIT_ASSERT( c2 );
+			CPPUNIT_ASSERT( p1.get( ) == c1.get( ) );
+			CPPUNIT_ASSERT( p2.get( ) == c2.get( ) );
+			CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
+			CPPUNIT_ASSERT_EQUAL( 2u , p2->getReferenceCount( ) );
+		}
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Atomic::cDel );
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Simple::cDel );
+		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );
+	}
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cDel );
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cDel );
+}
+
+void RefCountTest::testMoveCons( )
+{
+	P_Atomic p1{ P_Atomic::New( ) };
+	P_Simple p2{ P_Simple::New( ) };
+	{
+		P_Atomic c1{ std::move( p1 ) };
+		P_Simple c2{ std::move( p2 ) };
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cNew );
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cNew );
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Atomic::cDel );
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Simple::cDel );
+		CPPUNIT_ASSERT( c1 );
+		CPPUNIT_ASSERT( c2 );
+		CPPUNIT_ASSERT( !p1 );
+		CPPUNIT_ASSERT( !p2 );
+		CPPUNIT_ASSERT_EQUAL( 1u , c1->getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 1u , c2->getReferenceCount( ) );
+	}
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cDel );
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cDel );
+}
+
+void RefCountTest::testMoveAss( )
+{
+	P_Atomic p1{ P_Atomic::New( ) };
+	P_Simple p2{ P_Simple::New( ) };
+	{
+		P_Atomic c1;
+		P_Simple c2;
+		CPPUNIT_ASSERT( !c1 );
+		CPPUNIT_ASSERT( !c2 );
+		c1 = std::move( p1 );
+		c2 = std::move( p2 );
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cNew );
+		CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cNew );
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Atomic::cDel );
+		CPPUNIT_ASSERT_EQUAL( 0u , T_Simple::cDel );
+		CPPUNIT_ASSERT( c1 );
+		CPPUNIT_ASSERT( c2 );
+		CPPUNIT_ASSERT( !p1 );
+		CPPUNIT_ASSERT( !p2 );
+		CPPUNIT_ASSERT_EQUAL( 1u , c1->getReferenceCount( ) );
+		CPPUNIT_ASSERT_EQUAL( 1u , c2->getReferenceCount( ) );
+	}
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Atomic::cDel );
+	CPPUNIT_ASSERT_EQUAL( 1u , T_Simple::cDel );
+}