Pointer problems. (C++)

Started by
7 comments, last by jpetrie 17 years, 12 months ago
I am making a little pointer class to take care of pointers... it can auto-delete stuff for you if you wish with whatever you tell it to (p.set_destroyer(&operator delete)) and keeps reference counting, etc. etc.. (Thanks to rip-off for his pointer class to stand as a "learning base".) Now, I made some functions and the pointer is almost usable... Now the problem is, that with a little test class I got what was not expected (no deletion):

[[[test()]]][[[test()]]]

0 0[[[test()]]]

0 0x3d3d48[[[~test()]]]
Press ENTER to continue.

#include "Benefit_Pointer.h"
#include <iostream>
using namespace std;

struct test {
    test() { cout << "[[[test()]]]"; }
    test(const test&) { cout << "[[[test(const test&)]]]"; }
    ~test() { cout << "[[[~test()]]]"; }
};

int main(int argc, char **argv) {
    benefit::pointer<test> p;
    p.set_destroyer(&operator delete);

    p = new test();
    p.destroy();
    p = new test();
    benefit::pointer<test> pp = p;
    p.destroy();
    pp.destroy();

    cout << "\n\n" << p.data(false) << " " << pp.data(false);

    p = new test();
    test *rp = p;
    p.destroy();

    cout << "\n\n" << p.data(false) << " " << rp;
    delete rp;
}

I was expecting some more [[[~test()]]]s... So, it should have been:

con
des
con
des
con
des
Although it was:

con
con
con
des
So it seems that EVERYTHING works besides the pointer deleting the stuff... Here is what the pointer looks like (benefit_assert.h just defines ASSERT which becomes BENEFIT_ASSERT which becomes benefit::assert(val, __FILE__, __PRETTY_FUNCTION__, __LINE__)):

#ifndef BENEFIT_POINTER_H_
#define BENEFIT_POINTER_H_

#include "Benefit_Null.h"
#include "Benefit_Assert.h"

namespace benefit {
    template<class TYPE>
    class pointer {
    protected:
        class data_wrapper {
        protected:
            unsigned m_refcount;

            TYPE *m_data;
            pointer<TYPE> *m_currpointer;

        public:
            TYPE *data() { return m_data; }

            data_wrapper(TYPE *data): m_refcount(1), m_data(data), m_currpointer(NULL) {}
            ~data_wrapper() {
                ASSERT(m_refcount == 0);

                if (data() && m_currpointer && m_currpointer->get_destroyer())
                    (*(m_currpointer->get_destroyer()))(data());
            }

            void releaseref(pointer<TYPE> *currpointer) {
                if (currpointer != m_currpointer)
                    m_currpointer = currpointer;

                if (--m_refcount < 1)
                    delete this;
            }

            void addref() {
                ++m_refcount;
            }
        };

        mutable data_wrapper *m_data;
        void (*m_des)(void*);

    public:
        pointer(): m_data(NULL), m_des(NULL) {}
        pointer(TYPE *data): m_data(new data_wrapper(data)), m_des(NULL) {}
        pointer(const pointer<TYPE> &other): m_data(other.m_data), m_des(other.m_des) {
            if (m_data)
                m_data->addref();
        }

        ~pointer() {
            destroy();
        }

        pointer<TYPE> &operator =(const pointer<TYPE> &other) {
            if (m_data)
                m_data->releaseref(this);

            m_data = other.m_data;
            if (m_data)
                m_data->addref();

            return *this;
        }

        pointer<TYPE> &operator =(TYPE *other) {
            if (m_data)
                m_data->releaseref(this);

            m_data = (other) ? new data_wrapper(other) : NULL;

            return *this;
        }

        void set_destroyer(void (*des)(void*)) {
            m_des = des;
        }

        typedef void (*funcptr)(void*); // :D
        funcptr get_destroyer() { // :D
            return m_des;
        }

        TYPE *data(bool addref = true) {
            if (!m_data)
                return NULL;
            else
                if (addref)
                    m_data->addref();
            return m_data->data();
        }

        operator TYPE*() {
            return data();
        }

        TYPE *operator ->() {
            return data(false);
        }

        #if TYPE != void
        struct null_deref_exception {};
        TYPE &operator *() {
            if (!data(false))
                throw null_deref_exception;
            return *m_data->data(false);
        }
        #endif

        void destroy() {
            if (m_data)
                m_data->releaseref(this);
            m_data = NULL;
        }

        void destroy(void (*des)(void*)) {
            void (*old)(void*) = m_des;
            set_destroyer(des);
            destroy();
            set_destroyer(old);
        }
    };
}

#endif

Sorry for the long pointer class... I just have a 0 idea of why this is happening... I mean... I DID set the deleter, right? THANKS!
Advertisement
Boost Smart Pointers.
Quote:Original post by tstrimp
Boost Smart Pointers.


First of all, this is to learn... And it's fun. Second, I do NOT need boost right now JUST to use it's pointer class(es). EDIT: Besides, it's not just new/delete that I need, it's SDL_FreeSurface(), free(), FreeLibrary(), etc...

Any ideas?
I stepped through each line break-point by break-point and everything is fine (reference-counting, copys, etc.). The ONLY problem is that I see no destructors!

And yes, the m_currpointer isn't null nor is m_currpointer->get_deleter() - in fact, VC reports that it is accurately &operator delete! AND I'm pretty much sure it is getting called...

Any ideas?
operator delete frees memory. It does not call destructors.

Your pointer class never calls the destructors of the objects, only frees their memory.

The reason why the dtor gets called once is because you use delete rp;, which is a bit more like:

rp->~test();operator delete( rp );


Other than that, why can your pointer class change the method used to delete itself? It seems to make more sense to me for the method of destruction to be set at construction, and isn't going to change.

template< class Type >void defaultDelete( Type *type ){    delete type;}template< class Type >class pointer{    typedef void (*deleteType)( Type * );public:    pointer( Type *type, deleteType deleter = &defaultDelete<Type> )    {    }};int main( int argc, char **argv ){    pointer<int> regular( new int() ); // uses default delete    pointer<SDL_Surface> irregular( SDL_LoadBMP("stuff.bmp"), &SDL_FreeSurface );    //...}


I imagine that could work, but theres only one way to be sure.
Quote:Original post by rip-off
operator delete frees memory. It does not call destructors.

Your pointer class never calls the destructors of the objects, only frees their memory.

The reason why the dtor gets called once is because you use delete rp;, which is a bit more like:

rp->~test();operator delete( rp );


Other than that, why can your pointer class change the method used to delete itself? It seems to make more sense to me for the method of destruction to be set at construction, and isn't going to change.

*** Source Snippet Removed ***

I imagine that could work, but theres only one way to be sure.


But doesn't delete some_pointer; just change to operator delete(some_pointer);? Just like cout << my_int; would turn to <tt/>operator <<(cout, my_int);?
So basically what I'm asking is, is operator delete different from delete? I mean.. I'm confused...

As for the changing destroyers, I don't know. It's just fun, I guess :). Maybe because you can do something like this:
pointer<int> p = new int();p.destroy(&operator delete);p = &stack_int;// p's other destroyer was NULL, and since I only destroyed it with delete ONCE and then set it back (destroy(deleter) does it),// p now won't mess up and delete a stack allocated object


Thanks for helping!
Quote:

But doesn't delete some_pointer; just change to operator delete(some_pointer);



Nope. As you have yourself shown, the destructor isn't called. Otherwise you would be required to know the type in operator delete, to tell whether the destructor is existant( i.e for primitives ), or virtual.

delete does 2 things, calls the destructor, then calls operator delete on the "objects" raw address.

This is similar to new.

new does this:

- call operator new() with sizeof( Type ) as the argument.
- call the constructor specified, with any arguments
- cast the return value from operator new to a Type *

So no, delete and operator delete aren't exactly the same.
Quote:Original post by agi_shi
First of all, this is to learn... And it's fun. Second, I do NOT need boost right now JUST to use it's pointer class(es). EDIT: Besides, it's not just new/delete that I need, it's SDL_FreeSurface(), free(), FreeLibrary(), etc...

Learning is fine, but I will quote here from Scott Meyers:
Quote:Effective STL by Scott Meyers, Item 7
The STL itself contains no reference-counting smart pointer, and writing a good one - one that works correctly all the time - is tricky enough that you don't want to do it unless you have to. I published the code for a reference-counting smart pointer in More Effective C++ in 1996, and despite basing it on established smart pointer implementations and submitting it to extensive pre-publication reviewing by experienced developers, a small parade of valid bug reports has trickled in for years. The number of subtle ways in which reference-counted smart pointers can fail is remarkable. (For details, consult the More Effective C++ errata list.)

Fortunately, there's rarely a need to write your own, because proven implementations are not difficult to find. Once such smart pointer is shared_ptr in the Boost library...

Also note that boost::shared_ptr can be used just fine with pointers requiring deallocation via functions other than delete. Both the constructor and the reset member function are overloaded to take a deleter, which may be either a function or functor. On top of that, what is currently boost::shared_ptr will be included in the next C++ standard and is already available with some compilers as std::tr1::shared_ptr.

And as to not wanting to download Boost just for pointer classes, just do it. I held off from downloading Boost for a fair while because I wanted to do things myself. I regret having done so. Boost is awesome. Write your own implementations for learning purposes if you want, that's fine. But when it comes to actually using things, just use Boost. Your sanity is too important to waste chasing obscure bugs in a homegrown smart-pointer class when you have a perfectly good version already written for you.

Σnigma
Quote:
Enigma's post...


Hear hear!

This topic is closed to new replies.

Advertisement