Jump to content
  • Advertisement
Sign in to follow this  
Chris81

Yay or Nay on an object system that requires default constructors?

This topic is 4869 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, I just wrote an Object system based on Smart Pointers. There is an automatic templatized factory class that requires all objects to have a default constructor. The idea is to force all objects to be created using this ObjectFactory so that all objects are automatically garbage collected. This means that you can't have any other constructor other than the default, and all the ObjectFactory does is create the object on the heap, then return a smart pointer. Yay or nay on if this a good idea? Will it cause a lot of problems in the long run? As an example, I created a logger based on this system, which is a decent example because it has inheritance. LogFile and LogScreen both inherit from LogDest. There is a std::list of LogDests to send log messages to. (As a side note, all you have to do is run a INIT_OBJECT( ClassName ) macro in the header file. This will typedef the ClassNamePtr and create the ClassNameFactory.) Here is some code testing the usage:
	LogPtr log = LogFactory::Create();
	log->AddDest( LogFileFactory::Create(), "sound.log" );
	
	LogDestPtr lDestScreen = LogScreenFactory::Create();
	lDestScreen->SetPriorityThreshold( MEDIUM );
	log->AddDest( lDestScreen, "SOUND" );
	
	Test( "lDestScreen refcount = 2", lDestScreen->GetRefCount() == 2 );
	
	log->Error( "This is a test 1" );	// MEDIUM priority by default
	log->Error( "Yippee!!!", LOW );



This prints to the sound.log:
Sun Jun 19 23:46:01 2005 - LOG SYSTEM STARTED
Sun Jun 19 23:46:01 2005 - MEDIUM - This is a test 1
Sun Jun 19 23:46:42 2005 -    LOW - Yippee!!!
Sun Jun 19 23:46:42 2005 - LOG SYSTEM STOPPED



And this prints to the screen:
Mon Jun 20 00:07:49 2005 - SOUND LOG SYSTEM STARTED
lDestScreen refcount = 2 - SUCCESS
Mon Jun 20 00:07:49 2005 - SOUND -  MEDIUM - This is a test 1
Mon Jun 20 00:07:49 2005 - SOUND LOG SYSTEM STOPPED



So, what do you think? I'm off to bed, so I can't reply tonight but I will be on tomorrow. Thanks. - Chris

Share this post


Link to post
Share on other sites
Advertisement
Here are some similar, but alternitive approaches that let you use non-default constructors (using "new" too).


class Managed
{
protected:
Managed() { g_Manager.Register(this); }
~Managed() { g_Manager.UnRegister(this); }
};

class Log : public Managed
{
int foo;
public:
Log() : foo(5) {}
Log(int a) { foo = a*5; }
};

Log* log = new Log(8);
LogPtr log2(new Log(1234));


Basically, your constructor will go off and call the constructor for Manager, which will, in turn, register your object with whatever object-registering type thing you need.

Note that, because we're using "new", the user can also use "delete", so we want to have an unregister function. Because you're wanting to manage this with your manager, you may want to have UnRegister() assert an error if it is called except when the object is being deleted by the manager itself (as the user's code should not be calling delete on the object).

Unless you want to allow that - you'll still probably need some reference counting stuff that can generate errors - you don't want user code randomly deleting things that are in use.


If you require type information for your manager, then you can template your manager class. I use this method in many places and it is highly effective:


template <class T>
class Managed
{
protected:
// alternitive to a TypeInfo class could be to add the info
// as a static member to the Log class (ie: T::info)
// there are probably other alternitives as well.
Managed() { g_Manager.Register(this, TypeInfo<T>::info); }
~Managed() { g_Manager.UnRegister(this); }
};

class Log : public Managed<Log>
{
int foo;
public:
Log() : foo(5) {}
Log(int a) { foo = a*5; }
};


Another alternitive is to pass a value to a non-default Manager constructor:


class Managed
{
protected:
Managed(const TypeInfo& ti) { g_Manager.Register(this, ti); }
~Managed() { g_Manager.UnRegister(this); }
};

class Log : public Managed
{
int foo;
static TypeInfo loginfo;
public:
Log() : foo(5), Managed(loginfo) {}
Log(int a), Managed(loginfo) { foo = a*5; }
};



Of course, the globals and all that - are just examples. You'll want to drop in the relevent calls for your own managers and such.


Another important note: Be careful if Log gets created on the stack. You'll want your UnRegister function to be called without error.

Share this post


Link to post
Share on other sites
This is great stuff, thanks a ton. Did you get these methods from a Paper or Book? Or more likely just experience? :)

These alternatives definitely seem better than my approach. However, I guess in my thinking I was trying to avoid using new throughout the code. Actually, I was trying to make it so that new was only used in one place. The reason is because I planned on implementing a heap manager later that would be good for consoles. For stuff like memory recycling and auto defragmentation.

I probably should have mentioned that in the first post, but I was pretty tired.

Would it work to use one of your alternative approaches, and then override new for the heap manager?

Oh, and yeah, I have something similar to your second example in the Factory class for RTTI.

Thanks again.

-
Chris

Share this post


Link to post
Share on other sites
Yep - you can overload the new operator to provide more custom memory management. I think you can overload it in the parent class - allowing your child class to inherit and use it. With the template method - you could do some really special trickery with it.


I first saw the templated-inheritance method used to make a singleton class (I believe that it originates from GPG, although I just saw it on the 'net). The idea was the singleton functionality was in a Singleton class, and it was used like: "class Foo : public Singleton<Foo>", allowing you to simply go "new Foo(x, y)" (being a singleton - no need to assign it to anything) to create your singleton with some constructor of Foo.

I use templated inheritence for various other things in my code (for instance - a manager similar to what you might be doing - but for managing loading of disk-based resources). Templated-inheritance is a great way to do all kinds of clever RTTI-like tricks.

The other two non-templated methods are fairly simple derivitives of the templated method.

Share this post


Link to post
Share on other sites
I can't wait to get home from work to implement this, heh. I also have that singleton from GPG1 in my source, but I didn't think about other uses for that templatized inheritance...I need to analyze more and think differently that way.

So, if I override new on just the parent templatized managed object, then I can just use sizeof(T) to allocate the correct amount of memory?

Share this post


Link to post
Share on other sites
I'm not 100% sure on the inheritibility of operator new. I'm fairly sure it is inherited, but you'll probably want to check it or try it. (edit: after a quick hunt I couldn't find anything about its inhertability, so I would be surprised if it wasn't).

Additionally - operator new recieves a size (of type size_t), so you don't need to sizeof anything.

But yes - it sounds like you have the right idea, good luck with it [smile].

Share this post


Link to post
Share on other sites
Quote:
Original post by Andrew Russell
I'm not 100% sure on the inheritibility of operator new. I'm fairly sure it is inherited, but you'll probably want to check it or try it.


Haven't read the whole thread, anyways if you derive from a class that has overloaded new/new[]/delete/delete[] operators the compiler automatically gives you the correct size to allocate of the derived classes so you only need to overload them once for a class hierarchy.

There is even a special form of delete operator for class overloads only, that takes a size parameter which compiler also gives automatically e.g.


struct foo {

//....
void operator delete(void* p, std::size_t n) throw() { /*...*/ }

};


Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid
Haven't read the whole thread, anyways if you derive from a class that has overloaded new/new[]/delete/delete[] operators the compiler automatically gives you the correct size to allocate of the derived classes so you only need to overload them once for a class hierarchy.

Ah, thankyou snk [smile]. So yes, it is inherited and that solution should work, Chris.

Share this post


Link to post
Share on other sites
Either I'm missing something here, or this is exactly where templates are useful. Why can't you require GC'd classes to have a common base (say, GarbageCollectedObject) and then say:

template<typename Obj, typename P1>
Obj Create(P1 p1)
{
Obj* obj = new Obj(p1);
TrackObject(obj);
return obj;
}

template<typename Obj, typename P1, typename P2>
Obj Create(P1 p1, P2 p2)
{
Obj* obj = new Obj(p1, p2);
TrackObject(obj);
return obj;
}

// et cetera...

Share this post


Link to post
Share on other sites
Mostly because it's not as pretty.


// Compare:
FooPtr bar = new Foo(x, y);

// with:
FooPtr bar = Create<Foo, int, Leprechaun>(x, y);



There's no major technical reason to use either. Although yours does require the explicit specification of a type, which would make maintinence slightly harder.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!