I'm an IT student working on a system design project. It's a hierarchy of classes all inheriting from a base-class called Object (so we can use polymorphism). As part of the specs, we have a NullObject class that needs to be implemented as a singleton. The NullObject is used frequently in containers and is treated just like other Objects. This includes some deleting, which is where the problems start. I overloaded the delete operator to only delete the instance when a reference count is zero. It works fine if you only call delete as an operator, as in Object *obj = NullObject::getInstance(); //obj points to instance obj->delete(obj); but if you just call it as delete obj; it first calls the destructor. The memory isn't deallocated, but the NullObject loses its "objectness". If I have another pointer to the instance (which I have), it is no longer treated as a NullObject. (In my program, it gets treated as an Object, which is abstract - no good). Most singleton tutorials simply say that singletons don't usually get deleted in the middle of programs and don't address the issue at all. Is there a way to stop the destructor from being called? Or should I change all the deletes to operator deletes (overloaded for Object)?
How do you know the memory isn't deallocated? What exactly do you mean by "NullObject loses its 'objectness'"? Could you post the code for NullObject?
The overloaded delete operator for NullObject: void NullObject:perator delete (void* p) { if (count != 0) // count is a static int that gets ++-ed in getInstance() count--; if (count == 0) { :perator delete(p); } } Because the program deals with most classes as Objects (ie the base-class), delete is also overloaded for Object like this: void Object:perator delete (void* a) { Object* p = reinterpret_cast<Object*>(a); if ((p)->isNull()) // always returns true for NullObject, typeid for other { NullObject* no = dynamic_cast<NullObject*>(p); operator delete(no); } else :perator delete (a); } Basically, it checks if the Object is a NullObject. If it is, it calls the overloaded delete. Else, it calls the global delete. As I said before, it works when you don't just call delete, but call it as an operator. That's why I think the memory isn't deallocated. I can have multiple NullObjects, all pointing to the same instance, and when I delete one (using the long way) the others work fine. However, when I just go delete aNullObject; c++ automatically calls the destructor too (I can't remember the order in which things get called). That's where it falls apart. The NullObject is no longer seen as a NullObject (isNull() returns false).
I meant the whole code for NullObject, and you should use code blocks to avoid smilies and to preserve formatting. Have you tried making the destructor private, if you don't want people to be able to delete it? Then to delete the object people will be forced to call the appropriate function.
Oops, how do i do code blocks, same as dreamincode Code: ... ? Tried making the destructor private, but for some reason that did nothing. Maybe because all objects are seen as base-class Object, and delete is called from there, so it's legit? Either way, I'm looking for a way for delete to work, even on the singleton, but I guess it doesn't work that way. I'll post everything, but am running swiftly out of time, so I adapted it to a less cool but almost working version. Still, it's quite an interesting problem (to me at least) NullObject.h Code: #ifndef NULLOBJECT_H #define NULLOBJECT_H #include "Object.h" #include <iostream> using namespace std; /** * An Object that represents 'null'. */ class NullObject: public Object // has Singleton been implemented correctly? { private: static NullObject* instance; static unsigned count; protected: ~NullObject() {} NullObject() {} NullObject(const NullObject&); NullObject& operator = (const NullObject&); virtual int compareTo(const Object&) const; public: static NullObject* getInstance() { if (instance == 0 && count == 0) instance = new NullObject(); count++; return instance; } void operator delete (void*); virtual bool isNull() const; virtual void print(ostream& = cout) const; virtual Object* clone() const; virtual Object& assignment (const Object&); }; #endif NullObject.C: Code: #include "NullObject.h" #include <typeinfo> NullObject* NullObject::instance =NULL; unsigned NullObject::count = 0; /** * Checks if 'other' is another NullObject, and then compares accordingly. */ int NullObject::compareTo(const Object& other) const { if (typeid (NullObject) == typeid (other)) return 0; else throw ("incompatible_type_exception"); // the two types need to be identical } void NullObject::operator delete (void* p) { if (count != 0) count--; if (count == 0) { ::operator delete(p); } } NullObject::NullObject(const NullObject& other) { instance = other.instance; } NullObject& NullObject::operator = (const NullObject& other) { instance = other.instance; return *instance; } bool NullObject::isNull () const { return true; } void NullObject::print (ostream& out) const { out << "NullObject" << endl; } Object* NullObject::clone () const { return new NullObject(); } Object& NullObject::assignment (const Object& other) { if (typeid(other) == typeid(NullObject)) { return (*this); } else throw ("incompatible_type_exception"); }
I don't know how dreamincode works, but how to post code is outlined where you post a new thread: immediately below the title box there is the text "Before proceeding, please make sure you are aware of the Community Guidelines and you have read the Hints before you make a post thread." When posting to a forum you're new to, you should ALWAYS look for and read such things. It's not all legal mumbo jumbo, you get posting guidelines too. Code tags are number 7 of "Hints before you make a post", and it's called "hints ***BEFORE*** you make a post" for a good reason.