Thread: A Generic Reference-Counted Pointer Class (4 Bubba)

  1. #1
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981

    A Generic Reference-Counted Pointer Class (4 Bubba)

    I've implemented reference counting many times, but I've never done a generic implementation.

    This was inspired by the discussions in this thread.

    The main problem whith a generic reference-counted pointer class is knowing what to do once the reference count reaches zero. This may be calling delete for an allocated pointer, or performing some other cleanup routine.

    This implementation uses templates and function pointers to get the job done.
    Code:
    #include <iostream>
    #include <vector>
    using namespace std;
    
    template <class T>
    class RefCountedPtr
    {
    public:
        // prototype of function we will call when reference count reaches 0
        typedef void (*ZeroRefFunc_t)(T*);
    
    private:
        ZeroRefFunc_t m_0RefFunc;
        size_t *m_refs;
        T *m_pointer;
    
        // copy 'cpy' into '*this' and increment reference count
        RefCountedPtr& copy(const RefCountedPtr &rcp)
        {
            m_refs = rcp.m_refs;
            m_pointer = rcp.m_pointer;
            (*m_refs)++;
            return *this;
        }//copy
    
    public:
        // construction
        explicit RefCountedPtr(T *p, ZeroRefFunc_t f) 
            : m_0RefFunc(f), m_refs(new size_t(1)), m_pointer(p) {}
    
        // destruction
        ~RefCountedPtr() 
        {
            if (--(*m_refs) == 0)
            {
                delete m_refs;
                m_refs = 0;
                m_0RefFunc(m_pointer);
            }//if
        }//destructor
    
        // copy semantics
        RefCountedPtr(const RefCountedPtr &rcp) {copy(rcp);}
        RefCountedPtr& operator=(const RefCountedPtr &rcp) {return copy(rcp);}
    
        // pointer access
        T* operator->() {return m_pointer;}
    
    private:
        RefCountedPtr(); // must use explicit constructor
    };//RefCountedPtr
    
    
    struct A
    {
        A() {cout << "constructor" << endl;}
        ~A() {cout << "destructor" << endl;}
    };//A
    
    void DeleteA(A *p) {delete p;}
    
    int main()
    {
        RefCountedPtr<A> rcp(new A, &DeleteA);
    
        vector<RefCountedPtr<A> > v1;
        v1.push_back(rcp);
        v1.push_back(rcp);
        v1.push_back(rcp);
        v1.push_back(rcp);
    
        // make a copy of v1
        vector<RefCountedPtr<A> > v2 = v1;
    
        // when v1 and v2 go out of scope, the last instance of RefCountedPtr<A>
        // will call DeleteTest() for us
        return 0;
    }//maim
    gg
    Last edited by Codeplug; 12-15-2004 at 07:31 PM.

  2. #2
    S Sang-drax's Avatar
    Join Date
    May 2002
    Location
    Göteborg, Sweden
    Posts
    2,072
    Hopefully a pointer like this will make it to the C++ standard.

    Suggestions for improvements:
    • Instead of making the constructor take a function pointer, make the ZeroRef function a virtual member. That also makes it much easier to create a smart pointer when no ZeroRef function is needed.
    • Implement operator * and operator bool


    Code:
    private:
        RefCountedPtr(); // must use explicit constructor
    The user will be forced to use the explicit constructor even if you leave out this line.
    Last edited by Sang-drax; 12-15-2004 at 07:56 PM.
    Last edited by Sang-drax : Tomorrow at 02:21 AM. Reason: Time travelling

  3. #3
    Toaster Zach L.'s Avatar
    Join Date
    Aug 2001
    Posts
    2,686
    I might be missing something, but it appears that the reference count will not properly be updated if you reassign the smart pointer.

    Also, perhaps the ZeroRef function itself would go well as a template parameter (for a ZeroRef functor), with a default implementation.

    Cheers
    The word rap as it applies to music is the result of a peculiar phonological rule which has stripped the word of its initial voiceless velar stop.

  4. #4
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    Beat me to it. Actually, I was working on writing an abstract class that calls an overridden virtual 'cleanup' function when the reference count hits zero. This would allow 'easy' association of several pointers with the same reference counter. Unfortunately, as I conveniently forgot, you can't call virtual functions from base class constructors/destructors

    As I also forgot, you can create a struct containing all data members you want reference-protected, and just pass in that one struct. Poo.

    Here's the code I had before giving up (RCData is a test class I created to demonstrate the use of the base class):
    Code:
    class RefCounterBase
    {
    public:
    	RefCounterBase() :refCount(new unsigned long(1))
    	{}
    	RefCounterBase(const RefCounterBase& orig) :refCount(orig.refCount)
    	{ ++(*refCount); }
    	~RefCounterBase()
    	{ closeRef(); }
     
    	RefCounterBase& operator=(const RefCounterBase& orig)
    	{
    		if(orig.refCount == refCount)	//If assigning to itself, quit
    			return *this;
     
    		closeRef();
     
    		refCount = orig.refCount;
    		++(*refCount);
    		return *this;
    	}
     
    protected:
    	virtual void cleanup() = 0;
     
    private:
    	void closeRef()
    	{
    		if(--(*refCount) == 0)
    		{
    			delete refCount;
    			refCount = NULL;
    			cleanup();
    		}
    	}
    	unsigned long* refCount;
    };
     
    template <typename T>
    class RCData : public RefCounterBase
    {
    public:
    	RCData(const T* data) :	RefCounterBase(), data(data)
    	{}
    	RCData(const RCData& rc) :RefCounterBase(rc), data(rc.data)
    	{}
    	RCData& operator=(const RCData& rc)
    	{
    		RefCounterBase::operator=(rc);
    		data = rc.data;
    	}
     
    protected:
    	virtual void cleanup()
    	{
    		delete data;
    		data = NULL;
    	}
    	const T* data;
    };
     
    struct X
    {
    	int x, y;
    };
     
    int main()
    {
    	RCData<X> rc(new X());
    	return 0;
    }
    I'd just like to nitpick something of Codeplug's version: If you do an assignment, the reference count of the original pointer never gets decremented (and accordingly, the pointer doesn't get freed).

    >>make the ZeroRef function a virtual member.
    Sounds dangerous, that's what I tried with my own class.

    @Codeplug: You could also create an internal templated ZeroRef function (protected, static), use that as the default argument.
    Code:
    public:
    	explicit RefCountedPtr(T *p, ZeroRefFunc_t f = &DelOnZeroRef) //...
    protected:
    	template <class T> static void DelOnZeroRef(T* data) {delete data;}
    **EDIT**
    Beaten^2 Zach stole all my ideas.
    Last edited by Hunter2; 12-15-2004 at 08:32 PM.
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  5. #5
    Toaster Zach L.'s Avatar
    Join Date
    Aug 2001
    Posts
    2,686
    I'd just like to nitpick something of Codeplug's version: If you do an assignment, the reference count of the original pointer never gets decremented (and accordingly, the pointer doesn't get freed).
    That's what I just pointed out.

    Having said that, my thought for the solution was something like this (note, I did not actually implement the ref counted pointer, but it would essentially be Codeplug's version with a change):
    Code:
    template<class T>
    struct generic_delete
    {
       void operator()(T*& ptr) const
       {
          delete ptr;
          ptr = 0;
       }
    };
    
    template<class T, class ZeroRefT = generic_delete<T> >
    class ref_counted_ptr
    {
    /// ... Etc
    };
    * edit *
    If you read my code before I edited it, yes, it had a really stupid error.
    Last edited by Zach L.; 12-15-2004 at 08:36 PM.
    The word rap as it applies to music is the result of a peculiar phonological rule which has stripped the word of its initial voiceless velar stop.

  6. #6
    Toaster Zach L.'s Avatar
    Join Date
    Aug 2001
    Posts
    2,686
    Quote Originally Posted by Hunter2
    **EDIT**
    Beaten^2 Zach stole all my ideas.
    The word rap as it applies to music is the result of a peculiar phonological rule which has stripped the word of its initial voiceless velar stop.

  7. #7
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    Strange. I tried the templated functor idea, and for some reason the execution just skips right over the line (I tried putting a messagebox in the X destructor, and another messagebox in the functor's operator() code). As if it never existed, really - and, my compiler's warning me that 'ptr' is an unreferenced local variable "while compiling class template member function 'void RCSmartPtr<T>::closeRef(void)". But nothing looks broken about the code to me. Maybe it's a bug in the compiler?
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  8. #8
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    the problem is that you have several reference counts floating around for the same object, making it way too easy to destroy the object at the wrong time - you should have one and only one counter for each object. here's a memory manager using reference counts that might be interesting:

    and a driver to see how it works:

    Code:
    #include <string>
    #include <vector>
    #include <iostream>
    #include <pointer>
    
    using namespace std;
    using namespace psi;
    
    typedef user <string> base;
    typedef memory <string> data;
    
    void print(base & ptr) {
     string nil = "(NULL)";
     cout << "- " << (ptr ? *ptr : nil) << endl;
     }
    
    int main(void) {
     vector <data> mst;
     mst.push_back(new string("First"));
     mst.push_back(new string("Second"));
     mst.push_back(new string("Third"));
     mst.push_back(new string("Fourth"));
     vector <data::user> usr(mst.begin(), mst.end());
     cout << "Master List:" << endl;
     for_each(mst.begin(), mst.end(), print);
     cout << "User List:" << endl;
     for_each(usr.begin(), usr.end(), print);
     cout << "Erasing first element from master list." << endl;
     mst.erase(mst.begin());
     cout << "Master List:" << endl;
     for_each(mst.begin(), mst.end(), print);
     cout << "User List:" << endl;
     for_each(usr.begin(), usr.end(), print);
     cout << "Erasing NULL data from user list." << endl;
     usr.erase(remove(usr.begin(), usr.end(), (string*)NULL), usr.end());
     cout << "User List:" << endl;
     for_each(usr.begin(), usr.end(), print);
     return 0;
     }
    reference-counting does have a weakness, BTW - the user could possibly mess up the count by calling constructors/destructors manually. in that case you might want to replace the reference counting mechanism with something like an std::set...
    Last edited by Sebastiani; 12-15-2004 at 10:15 PM.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  9. #9
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    reference-counting does have a weakness, BTW - the user could possibly mess up the count by calling constructors/destructors manually.
    While explicit destructor calls can mess with the counting (solution: check and set null in destructor), explicit constructors can't really - the only way to call the constructor manually is to use placement new, and placement new over an initialized object is undefined behaviour anyway.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  10. #10
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    that's a good point. in fact, it's the destructor I was worried about most - you're solution seems to solve that problem, though. come to think of it, you could prevent both from happening by using a single boolean flag - if it's set in the constructor, return, otherwise increment the RC and set the flag; if it's cleared in the destructor, return, otherwise decrement the RC and clear the flag.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  11. #11
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    if it's set in the constructor, return, otherwise increment the RC and set the flag;
    You can't do that. As the object is originally constructed, the memory it is constructed in has undefined contents, thus the boolean flag may easily appear to be set.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  12. #12
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    ah, right.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  13. #13
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    I'm afraid you'll just have to trust the programmer not to construct one object right over another. Personally I don't have any problems with this. If someone memsets over your object, do you feel responsible if it doesn't work anymore?
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  14. #14
    Registered User
    Join Date
    Aug 2003
    Posts
    470
    You should be able to pass the onZeroRef function as a template taking as an argument the object. Then you might be able to create a default behavior where a delete functor is called.

  15. #15
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    I'd use a Boost.Function object to store the deleter. This has the advantage of not introducing another template parameter to the smart pointer while allowing any kind of deleter.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Pointer to a class inside the WindowsProcedure function
    By like_no_other in forum Windows Programming
    Replies: 3
    Last Post: 06-01-2009, 12:52 PM
  2. C OpenGL Compiler Error?
    By Matt3000 in forum C Programming
    Replies: 12
    Last Post: 07-07-2006, 04:42 PM
  3. Calling a class member through a pointer to a class in a DLL
    By cboard_member in forum C++ Programming
    Replies: 1
    Last Post: 04-19-2006, 10:55 AM
  4. My Window Class
    By Epo in forum Game Programming
    Replies: 2
    Last Post: 07-10-2005, 02:33 PM
  5. qt help
    By Unregistered in forum Linux Programming
    Replies: 1
    Last Post: 04-20-2002, 09:51 AM