Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid object slicing for non-virtual destructors

I am writing code for smart pointers as an exercise. Using tutorials online (1 , 2) I have developed a normal smart-pointer class with reference counting. The problem is I am unable to figure out the following:

when the smart pointer detects that no more references exist to a particular object, it must delete the object via a pointer to the original type, even if the template argument of the final smart pointer is of a base type. This is to avoid object slicing for non-virtual destructors.

How can I achieve that. Basically my code looks like below (from the tutorial).

template < typename T > class SP
{
private:
    T*    pData;       // pointer
    RC* reference; // Reference count

public:
    SP() : pData(0), reference(0) 
    {
        // Create a new reference 
        reference = new RC();
        // Increment the reference count
        reference->AddRef();
    }

    SP(T* pValue) : pData(pValue), reference(0)
    {
        // Create a new reference 
        reference = new RC();
        // Increment the reference count
        reference->AddRef();
    }

    SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)
    {
        // Copy constructor
        // Copy the data and reference pointer
        // and increment the reference count
        reference->AddRef();
    }

    ~SP()
    {
        // Destructor
        // Decrement the reference count
        // if reference become zero delete the data
        if(reference->Release() == 0)
        {
            delete pData;
            delete reference;
        }
    }

    T& operator* ()
    {
        return *pData;
    }

    T* operator-> ()
    {
        return pData;
    }
    
    SP<T>& operator = (const SP<T>& sp)
    {
        // Assignment operator
        if (this != &sp) // Avoid self assignment
        {
            // Decrement the old reference count
            // if reference become zero delete the old data
            if(reference->Release() == 0)
            {
                delete pData;
                delete reference;
            }

            // Copy the data and reference pointer
            // and increment the reference count
            pData = sp.pData;
            reference = sp.reference;
            reference->AddRef();
        }
        return *this;
    }
};

EDIT:

To achieve that I have to have a pointer to the original type.

I have posted a question here: delete via a pointer to Derived, not Base

But Now since viewing the comments and answers I think both are related. I have the constructor:

template <typename T>
template <typename U>
Sptr<T>::Sptr(U* u) : obj(u),ref(NULL) {
    //do something
    ref = new RC();
    ref->AddRef();
}

Now consider Sptr<Base1> sp(new Derived); where Derived is derived from Base1. Base1 has protected constructor/destructor. Which is storing for an object of type T But I need to store it through an object of type U. I need to preserve that. How can I do that?

like image 907
footy Avatar asked Apr 08 '13 18:04

footy


1 Answers

Your smart pointer needs 3 chunks of information.

First, the pointer to the data (T* or something).

Second, your reference count: std::atomic<int> or something.

Third, your destruction function (std::function<void(T*)> or something).

When the smart pointer is first created, that destruction function is created. When your smart pointer is copied to another smart pointer, this destruction function is copied. If the type of the new smart pointer doesn't match the old, that destruction function gets wrapped up in a type-compatible way (does std::function<void(Base*)> = std::function<void(Derived*)> work out of the box? Regardless, you are basically doing that).

By default, this destruction function is just delete t, but as a side benefit, this allows the users of your smart pointer to pass in a destruction function, which isn't always delete t.

Amusingly, on the equivalent of reset, you replace your destruction function. So you could actually make the signature of the destruction function be std::function<void()>, which makes moving it between T and U type smart pointers a tad easier.

template < typename T > class SP
{
private:
  T*    pData;       // pointer
  RC* reference; // Reference count
  std::function<void()> destroyData;
public:
  template<typename U>
  SP(U* pValue):
    pData(pValue),
    reference(nullptr),
    // store how to destroy pValue now, for later execution:
    destroyData([pValue]()->void{
      delete pValue;
    })
  {
    // Create a new reference 
    reference = new RC();
    // Increment the reference count
    reference->AddRef();
  }
  // similar for operator=, and you may have to do something for SP<T> as well:
  template<typename U>
  SP(const SP<U>& sp):
    pData(sp.pData),
    reference(sp.reference),
    destroyData(sp.destroyData)
  {
    // Copy constructor
    // Copy the data and reference pointer
    // and increment the reference count
    reference->AddRef();
  }
  template<typename U>
  SP<T>& operator = (const SP<U>& sp)
  {
    // blah blah blah, then
    destroyData = sp.destroyData;
  }

  ~SP()
  {
    // Destructor
    // Decrement the reference count
    // if reference become zero delete the data
    if(reference->Release() == 0)
    {
        delete reference;
        destroyData(); // here I destroyed it!
    }
  }
};

or something like that

like image 55
Yakk - Adam Nevraumont Avatar answered Sep 28 '22 11:09

Yakk - Adam Nevraumont