Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// smart_ptr_deleters.cpp
// shows how to use custom deleters with unique_ptr and shared_ptr
// Bartlomiej Filipek, April 2016, bfilipek.com
#include <iostream>
#include <memory>
#include <functional>
#include <cassert>
// This class cannot be changed, might come from 3rd party library
// to delete it completely you need to call ReleaseElements first!
class LegacyList {
public:
LegacyList() { s_refCounter++; }
~LegacyList() { s_refCounter--; }
LegacyList(const LegacyList&) { s_refCounter++; }
LegacyList & operator= ( const LegacyList & ) { s_refCounter++; return *this; }
void Add(const std::string& str ) { }
void Remove(const std::string& str ) { }
void ReleaseElements() { }
static int GetGlobalObjectRefCount() { return s_refCounter; }
private:
static int s_refCounter;
};
int LegacyList::s_refCounter = 0;
// old style Class, with explicit new/delete/raw pointers
class WordCache {
public:
WordCache() { m_pList = nullptr; }
~WordCache() { ClearCache(); }
void UpdateCache(LegacyList *pInputList) {
ClearCache();
m_pList = pInputList;
if (m_pList)
{
// do something with the list...
}
}
private:
void ClearCache() { if (m_pList) { m_pList->ReleaseElements(); delete m_pList; m_pList = nullptr; } }
LegacyList *m_pList; // owned by the object
};
// stateless functor
struct LegacyListDeleterFunctor {
void operator()(LegacyList* p) {
p->ReleaseElements();
delete p;
std::cout << "LegacyListDeleterFunctor..." << std::endl;
}
};
// normal free function used to delete LegacyList
void DeleteLegacyList(LegacyList* p) {
p->ReleaseElements();
delete p;
std::cout << "DeleteLegacyList function..." << std::endl;
}
// Modern version of the cache, we use std::unique_ptr with custom deleter to handle deletion of LegacyList
// we can remove default constructor and destructor, since unique_ptr will do this for us
// also we could remove ClearCache method (since it was only for conveniance)
class ModernWordCache {
public:
// different options for custom deleters
using unique_legacylist_ptr_stdfunc = std::unique_ptr<LegacyList, std::function<void (LegacyList*)>>;
using unique_legacylist_ptr_functor = std::unique_ptr<LegacyList, LegacyListDeleterFunctor>;
using unique_legacylist_ptr_function = std::unique_ptr<LegacyList, void (*)(LegacyList *)>;
using unique_legacylist_ptr = unique_legacylist_ptr_functor;
public:
void UpdateCache(unique_legacylist_ptr pInputList) {
m_pList = std::move(pInputList);
// do something with the list...
}
private:
//unique_legacylist_ptr m_pList { nullptr, &DeleteLegacyList };
unique_legacylist_ptr m_pList;
};
// Modern version that uses shared_ptr
class ModernSharedWordCache {
public:
void UpdateCache(std::shared_ptr<LegacyList> pInputList) {
m_pList = pInputList;
// do something with the list...
}
private:
std::shared_ptr<LegacyList> m_pList;
};
void TestOldWay()
{
WordCache myTestClass;
LegacyList* pList = new LegacyList();
// fill the list...
myTestClass.UpdateCache(pList);
LegacyList* pList2 = new LegacyList();
// fill the list
myTestClass.UpdateCache(pList2);
}
void TestUniqueModernWay()
{
ModernWordCache myModernClass;
// whe nunique_legacylist_ptr uses a function, then we have to pass it in constructor
// ModernWordCache::unique_legacylist_ptr pUniqueList(new LegacyList(), &DeleteLegacyList);
// when nunique_legacylist_ptr uses a functor, functor object is created by default...
ModernWordCache::unique_legacylist_ptr pUniqueList(new LegacyList());
myModernClass.UpdateCache(std::move(pUniqueList));
//ModernWordCache::unique_legacylist_ptr pUniqueList2(new LegacyList(), &DeleteLegacyList);
ModernWordCache::unique_legacylist_ptr pUniqueList2(new LegacyList());
myModernClass.UpdateCache(std::move(pUniqueList2));
}
void TestSharedModernWay()
{
ModernSharedWordCache mySharedClass;
mySharedClass.UpdateCache(std::shared_ptr<LegacyList>(new LegacyList(), LegacyListDeleterFunctor()));
mySharedClass.UpdateCache(std::shared_ptr<LegacyList>(new LegacyList(), DeleteLegacyList));
// simple example with custom deleter for <int>
std::shared_ptr<int> pIntPtr(new int(10), [](int *pi) { delete pi; });
}
int main()
{
std::cout << "sizeof(int*) = " << sizeof(int*) << '\n';
// size of unique_ptr will be 4 or 8 bytes
std::cout << "sizeof(std::unique_ptr<LegacyList>) = " << sizeof(std::unique_ptr<LegacyList>) << '\n';
// std::function is a bit heavy, around 32 bytes! (on x64)
std::cout << "sizeof(std::function<void (LegacyList*)>) = " << sizeof(std::function<void (LegacyList*)>) << '\n';
std::cout << "sizeof(unique_legacylist_ptr_stdfunc) = " << sizeof(ModernWordCache::unique_legacylist_ptr_stdfunc) << '\n';
// with normal function pointer we have two pointers to store, so 8 or 16 bytes
std::cout << "sizeof(unique_legacylist_ptr_function) = " << sizeof(ModernWordCache::unique_legacylist_ptr_function) << '\n';
// if we use stateless functor then the compiler will probably use Empty Base Class optimization and thus we use only 8 bytes(like normal unique_ptr) !!
std::cout << "sizeof(unique_legacylist_ptr_functor) = " << sizeof(ModernWordCache::unique_legacylist_ptr_functor) << '\n';
// shared_ptr does not include deleter in type definition, size is always the same
std::cout << "sizeof(std::shared_ptr<LegacyList>) = " << sizeof(std::shared_ptr<LegacyList>) << '\n';
auto IntDel = [](int *p) { delete p; };
std::cout << "sizeof(std::unique_ptr<int, decltype(IntDel)>) = " << sizeof(std::unique_ptr<int, decltype(IntDel)>) << '\n';
assert(LegacyList::GetGlobalObjectRefCount() == 0);
TestOldWay();
assert(LegacyList::GetGlobalObjectRefCount() == 0);
assert(LegacyList::GetGlobalObjectRefCount() == 0);
TestUniqueModernWay();
assert(LegacyList::GetGlobalObjectRefCount() == 0);
assert(LegacyList::GetGlobalObjectRefCount() == 0);
TestSharedModernWay();
assert(LegacyList::GetGlobalObjectRefCount() == 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.