std::shared_ptr Wrapper
Home -->
Programming Projects -->
FPS Game Code -->
Memory Code -->
std::shared_ptr Wrapper
The Goal
C++ std::shared_ptr pointers are great.
When we use the std::shared_ptr pointers to point
to an object, the object will be deleted precisely
when the last std::shared_ptr to the object is deleted.
The purpose of this page is to create a wrapper
for this pointer that prints warning messages to try to
get the caller to track the reference count.
For example, our wrapper class has a member function
called Delete.
This function first makes sure the reference count is 1
(otherwise it prints a warning message).
It then sets the std::shared_ptr member to null.
The reason we print warnings instead of printing
error messages and then exiting the program is because
the reference count is only an approximation
(because of multithreading).
An opposing point of view is the following:
"Why care about when the destructor of the object gets called?
It gets called when it gets called."
However our wrapper class is useful to try to make sure that
the destructor is called
exactly when we want it to.
Data Members of the Class
The class has a the following data member:
std::shared_ptr m_ptr;
It also has the following data member:
std::string m_name;
That is, we give every pointer a string name
which is printed in warning messages.
The Member Functions of the Class
Here are all the main member functions of our pointer class:
- The default constructor.
- The copy constructor.
- The destructor.
If the reference count is > 0 at the beginning of this function,
it prints a warning.
- The "cast to bool" function.
- A function to assert that the raw pointer is non-null.
- The function "Raw" which returns the raw pointer.
- The function "GetRefCount" which gets (an approximation for)
the reference count of m_ptr.
- The assignment operator.
- The -> operator.
- The * operator.
- The function "IsNull".
- The function "SetNew" which initializes the pointer wrapper to point to an object.
- Another version of the "SetNew" which sets m_name.
- The function "GetName" to get m_name.
- The function "SetName" to set m_name.
- The function "Drop" which sets m_ptr to null
and does not print any warning.
- The function "SetNull" which sets m_ptr to null,
but before doing this it makes sure the reference count is > 1
(otherwise it prints a warning).
- The function "Delete", which sets m_ptr to null,
but before doing this it makes sure the reference count is exactly 1
(otherwise it prints a warning).
- The function "DeleteMaybe", which sets m_ptr to null,
but before doing this it makes sure the reference count is ≤ 1
(otherwise it prints a warning).
Outside the class there is a helper function
to create a null wrapper class pointer.
The Code
//File: SharedPtrWrapper.h
#ifndef SharedPtrWrapper_h_
#define SharedPtrWrapper_h_
#include <memory> //For "std::shared_ptr".
#include <string>
#include <iostream>
using std::string;
using std::cout;
//-----------------------------------------------
//
// THREAD SAFE POINTER
//
//-----------------------------------------------
//When constructed using the default constructor,
//it is a null pointer.
template <class T>
class SharedPtrWrapper {
private:
std::shared_ptr<T> m_ptr;
//For debugging.
string m_name;
void DerefError1() const {
cout
<< "*** Error: Thread Safe Pointer Error:\n"
<< " Attempt to use -> on null SharedPtrWrapper\n";
cout
<< " Pointer name: " << m_name << "\n";
exit(0);
}
void DerefError2() const {
cout
<< "*** Error: Thread Safe Pointer Error:\n"
<< " Attempt to dereference null SharedPtrWrapper\n";
string name = "\"" + m_name + "\"";
cout
<< " Pointer name: " << m_name << "\n";
exit(0);
}
public:
SharedPtrWrapper() {
m_ptr = nullptr;
}
SharedPtrWrapper(const SharedPtrWrapper<T> & b) {
this->m_ptr = b.m_ptr;
this->m_name = b.m_name;
}
~SharedPtrWrapper() {
if( this->GetRefCount() == 1 ) {
cout << "*** Warning: Destructing a Thread Safe Pointer\n"
<< " with a ref_count of 1. This could be a \"leak\".\n"
<< " Note that the ref_count is just an approximation.\n"
<< " name = " << m_name << "\n";
}
}
operator bool() const {
return (bool)m_ptr;
}
//"Assert non-null".
//Will exit if the raw ptr is null.
void ANN() const {
if( m_ptr ) return;
cout << "*** Error: \"assert non-null\" "
<< "failed in thread safe pointer.\n"
<< "Pointer name = \"" << m_name << "\"\n";
exit(0);
}
//In general, we want to avoid this.
//Normally, all SharedPtrWrappers should be set to null
//except the last one, which is deleted.
//This is a backup method, where we can simply drop
//each SharedPtrWrapper and there will not be a warning.
void Drop() {
m_ptr = nullptr;
}
//Could first check if ref count is > 1,
//and otherwise say there is a "leak".
void SetNull() {
if( this->GetRefCount() == 1 ) {
cout << "*** Warning: Setting a SharedPtrWrapper to null\n"
<< " where the pointer has a ref_count of 1.\n"
<< " This could be a \"leak\".\n"
<< " Use Delete instead of SetNull on the last SharedPtrWrapper to an object.\n"
<< " Note that the ref_count is just an approximation.\n"
<< " name = " << m_name << "\n";
}
m_ptr = nullptr;
}
T * Raw() const {
return m_ptr.get();
}
//Only an approximation.
int GetRefCount() const {
if( m_ptr ) {
return m_ptr.use_count();
} else {
return 0;
}
}
SharedPtrWrapper<T> & operator=(const SharedPtrWrapper<T> & b) {
m_ptr = b.m_ptr;
m_name = b.m_name;
return *this;
}
T *operator->() const {
if( !m_ptr ) {
DerefError1();
}
return m_ptr.get();
}
T &operator*() const {
if( !m_ptr ) {
DerefError2();
}
return *(m_ptr.get());
}
bool IsNull() {
return !(bool)m_ptr;
}
void SetNew(T * ptr) {
if( m_ptr ) {
cout << "*** Error: SharedPtrWrapper error:\n"
<< " m_ptr is non null in SetNew\n"
<< " old_name = " << m_name << "\n";
}
this->m_ptr = std::shared_ptr<T>(ptr);
}
void SetNew(
T * ptr,
const string & name)
{
if( m_ptr ) {
cout << "*** Error: SharedPtrWrapper error:\n"
<< " m_ptr is non null in SetNew\n"
<< " old_name = " << m_name << "\n"
<< " new_name = " << name << "\n";
}
this->m_ptr = std::shared_ptr<T>(ptr);
this->m_name = name;
}
//If it had a name but now it is null,
//this is the last name.
string GetName() const {
return this->m_name;
}
void SetName(const string & name) {
this->m_name = name;
}
//Does not actually delete, just sets m_ptr to null.
void Delete() {
if( !m_ptr ) {
cout << "*** Error: trying to delete a null SharedPtrWrapper.\n";
return;
}
int ref_count = this->GetRefCount();
//Will actually delete if m_ptr is the
//only shared_ptr to the object.
m_ptr = nullptr;
if( ref_count > 1 ) {
cout << "*** Warning: SharedPtrWrapper has ref_count = "
<< ref_count << " > 1\n"
<< " just before delete.\n"
<< " Note that the ref_count is just an approximation.\n"
<< " name = " << m_name << "\n";
}
}
void DeleteMaybe() {
if( m_ptr ) this->Delete();
}
};
template <class T>
SharedPtrWrapper<T> NULL_SHARED_PTR_WRAPPER() {
SharedPtrWrapper<T> temp;
return temp;
}
#endif
Example 1
void SharedPtrWrapperTest1() {
SharedPtrWrapper<int> x;
x.SetNew(
new int,
"my first int");
SharedPtrWrapper<int> y;
y = x;
// y.DeleteMaybe(); //This would be a warning.
// y.Delete(); //This would also be a warning.
//Ref count is 2.
y.SetNull(); //This is ok.
//Ref count is 1.
x.Delete(); //This is ok.
//Ref count is 0.
}
Example 2
SharedPtrWrapper<double> x;
x.SetNew(
new double,
"my first double");
SharedPtrWrapper<double> y;
y = x;
//Ref count is 2.
y.Drop(); //This is ok.
//Ref count is 1.
x.Drop(); //This is ok.
//Ref count is 0.