Sign in to follow this  
freeworld

quick question about inheritance and static members

Recommended Posts

I'm trying to make a simple state machine that uses inheritance, more as a pratice to better understand virtual function and inheritance it's self. so I've thought of a class like this that would store other classes of its self and could push and pop classes from a static stack. Am I doing this right?
class CTask
{
public:

// my understanding is that if I declare it static only one instance of it 
// should ever be created, but all instances of the CTask should have access 
// to it?
     static std::vector<CTask*> m_tasks;

     virtual CTask() = 0;
     virtual ~CTask() = 0;
     virtual void Init() = 0;
     virtual void Update() = 0;
     virtual void Display() = 0;
     virtual void Free() = 0; 

     void Push(CTask*);
     void Pop(CTask*);
};

would that be the prefered way to do it, or should I make seperate classes for CTask and CTask_Stack?

Share this post


Link to post
Share on other sites
You're right that all tasks, and subclasses thereof, would share the same m_tasks. But why should there be exactly one task container per executable process? Why is the Task class responsible for scheduling and managing Tasks as well as being those tasks? Think about exactly what the responsibility of CTask is, and exactly what the responsibility of other classes should be.

Share this post


Link to post
Share on other sites
I havn't realy thought it out much, but what I was trying to accompolish was an underlining interface for a simple game that could run on it's own without having to use "switch case" statements wich always lead to continuos updateing everytime I add a new component of the game... ad for me things always get cluttered that way. I also wanted to be able to incorporate the same kind of system for a scene graph and GUI's (ofcourse they'd be they're own systems altogether but they'd be designed similar)

As for having only one CTask container, it made sense in my first draft, but now that you bring it up I can already see ways it might be better to have seperate containers. Such as freeing groups of tasks would be alot easier if the parent task that was closing them had them in it's container. Then again it'll probably already have knowledge of all the CTasks that it's created anyways. Or maybe having it's own container would allow branching... Cause I what to have a sort of parent->child->child relation of everything, so if one thing gets updated, then it updates all its childs, same when it get's freed it'll free all its childs aswell.

IDK?

Share this post


Link to post
Share on other sites
I personally would create a separate manager class for the tasks. This will allow you to separate management of the tasks.

Also, this would also allow multiple managers, better encapsulation, cleaner code, and no need for static...

Also, I would need to discourage using Push() and Pop() for routine names as they do not create a coherent interface. Better names would be "Add", "AddTask"--as long as it is at the same abstraction level the rest of the interface is.

Share this post


Link to post
Share on other sites
I little more tinkering around, and here is why I still don't understand why a seperate manager would be worth it.

As far as I know a seperate manager that controls the stack, adds and removes tasks from the stack would have to know about all the possible tasks that would be added to the stack in order to add them to the stack... right?

with the task it'self being the container it only needs to know about the types of tasks that it deals with.

or am I missing something?

Share this post


Link to post
Share on other sites
Quote:

As far as I know a seperate manager that controls the stack, adds and removes tasks from the stack would have to know about all the possible tasks that would be added to the stack in order to add them to the stack... right?

Incorrect. This is where the beauty of inheritance comes in. All we need to do is have the TaskManager class store a list of CTask* pointers. We can use a simple cast to convert a superclass (Classes derived from CTask) to its base class and vice versa.

For example...

CTask* task = new CSomeClassThatDerivesFromCTask;

This pointer now points to a derived task class object. We can also call any derived class routines through this base pointer as well, so long as it is defined. Making a routine pure virtual forces this rule, as it will display an error if the routine is not defined in a derived class.

Because of this, all the task manager needs to work on is a CTask*, which can represent any type of task.

Share this post


Link to post
Share on other sites
Well the way I see it is the tasks themselves are going to create the new tasks then pass this new class onto the manager... correct?

example;

CTask could have something in it's update that would would look like

*CTaskTemp = new CTask;

That's all dandy but how would the manager know this is happening in a way that it makes things clear, and seperated. It seems either way the manager and the task are going to be very intertwined anyways. Also usuing the seperate manager, how would I still incorporate branching?

hopefully this stays formatted enough

Task01
|
-----------
| |
Task02 Task03
|
Task04
|
Task05


The way I wanted, it would still be like a stack were everything gets called in order, but with branching I could seperate things into groups. This would allow for example, If I paused Task02, then Task04 and Task05 would puase aswell cause there parents were paused and not calling them, but Task03 would still be called and could branch off into seperate groups.

Share this post


Link to post
Share on other sites
Quote:

That's all dandy but how would the manager know this is happening in a way that it makes things clear, and seperated.

Here is an example of what I mean. Please note that the following code is only for demonstration purposes, and has not been compiled nor tested, but hopefully contains no errors :)

/**
* Base Task abstract object
*/

class Task {

public:
virtual Task() = 0;
virtual ~Task() = 0;
virtual void Init() = 0;
virtual void Update() = 0;
virtual void Display() = 0;
virtual void Free() = 0;
};

/**
* System Init Task
*/

class InitTask : public Task {

public:

// these routines must be defined. If they are not,
// compiler will issue errors.

virtual InitTask();
virtual ~InitTask();
virtual void Init();
virtual void Update();
virtual void Display();
virtual void Free();
};

/**
* Task Manager
*/

class TaskManager {

std::vector <Task*> m_pTasks;

public:

void AddTask (Task* pTask) {

if (pTask) {
m_pTasks.push_back (pTask);
pTask->Init ();
}
}
}


Assuming the above is your code, we can do this to create a new task:

TaskManager* _manager=new TaskManager;

// Creates new initialization task for the manager to use. Careful about
// using this method
_manager->AddTask (new InitTask);

// Creates new initialization task
InitTask* newTask = new InitTask;
_manager->AddTask (newTask);

// Now, we can have the manager call each tasks virtual methods to set up each
// task through its base Task* pointer. For example...

// Update tasks
_manager->Update (); // calls each tasks Update() method through base pointer.
// This works thanks to C++'s vtable implementation


I hope this clarifies my idea a little bit :)

Share this post


Link to post
Share on other sites
Quote:

call me dense, but the _manager pointer would this be store in the task to be able to call AddTask()?

Thats not needed. The code I posted works do to the use of inheritance.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this