instance ID (many questions...)

Started by
4 comments, last by Gabry Hyrule 17 years, 6 months ago
Hello everybody, I have some questions about the whole process of identification of objects. Let's put ourselves in the context of my problem... I have a "bullet" class, which inherits about everything from a "OBJECT_BASE" class (which contains -x, y, direction-). The only new functions in this bullet class is a "step" and a "draw" function. I would like to call these functions in every steps (every loops) of my program. To avoid a lot of mess, I tried to use an array of pointers to the bullet objects. My first question: is this declaration valid? BulletObj* bulletInstances[148]; 148 is the worst case scenario in which there is 148 bullets in the screen (the number is based on the bullets' speed, the screen radius and some other stuffs). If I'm right, this will create 148 NULL pointers, able to point to the bullet objects. If all is right, I can now introduce my class and it's constructor and destructor:
class BulletObj: public OBJECT_BASE
{
    public:
        void step(void);
        void draw(void);
        
        BulletObj();
        ~BulletObj();
};

BulletObj::BulletObj()
{
    for (unsigned int iCheck = 0; iCheck < 405; iCheck++){
        if (*bulletInstances[iCheck] = NULL){
            ID = iCheck;
            *bulletInstances[iCheck] = &this
            break;
        }
    }
}

BulletObj::~BulletObj()
{
    *bulletInstances[ID] = NULL;
}
Okay, in the constructor, I check in the pointers array if there is a NULL one, which isn't pointing to another previous bullet, and if there is one, I replace the object's ID with the index, and place the address of the object in the array. Then the break is "supposed" to quit the loop and terminate the constructor. When my bullet will be out of the view, I will call the destructor (other question...) with this: bulletInstances[currentStep].~BulletObj(); But I'm pretty sure this will get my compiler really mad. Anyways, in the destructor, I just replace the pointer with a NULL one so it can accept a new bullet. Finally, why all this... can I call the step/draw functions like this:?
for (int currentStep = 0; currentStep < 148; currentStep++){
   bulletInstances[currentStep].step();
}
Ok, well, you can see my understanding of pointers is... low... So, if someone can help me, a great thanks to you! Gabry Hyrule ps: sorry for my bad english, my teachers were so bad :) ps2: If someone is interested by my OBJECT_BASE class:
class OBJECT_BASE
{
    /*friend FUNCTION TO  CHECK COLLISION*/
    private:
        unsigned int ID;
        int x, y;
        double direction;
    
    public:
        OBJECT_BASE(int Nx, int Ny, double Ndirection);
};
Edit by Fruny: code formatting. [Edited by - Fruny on October 16, 2006 12:51:52 PM]
I'm 15, and I know C++, OpenGL and a little Windows programming.
Advertisement
Quote:
I have a "bullet" class, which inherits about everything from a "OBJECT_BASE" class (which contains -x, y, direction-). The only new functions in this bullet class is a "step" and a "draw" function.

This is probably poor design (it doesn't separate responsibility, it smacks of potential unneccessary inheritance,...).

Quote:
My first question: is this declaration valid?

Yes, it is legal C++.

Quote:
BulletObj* bulletInstances[148];

148 is the worst case scenario in which there is 148 bullets in the screen (the number is based on the bullets' speed, the screen radius and some other stuffs). If I'm right, this will create 148 NULL pointers

You're wrong: it creates an array of 148 pointers...which are not neccessarily initialized to anything (depending on where the declaration is), much less the null pointer value. Furthermore, making worst-case assumptions and hard-coding them is generally brittle, especially using magic numbers for the worst-case upper boundary. Use a std::vector of BulletObj* instead, it will be much better.

In fact, your code already has a bug related to that issue. See here:
Quote:
for (unsigned int iCheck = 0; iCheck < 405; iCheck++)


This will cause you to index beyond the boundary of the array. Bang, you're dead.

Quote:
if(*bulletInstances[iCheck] = NULL)
*bulletInstances[iCheck] = &this

Your code also uses bulletInstances incorrectly, as illustrated by these lines. You are doing "*bulletInstances[n]" and expecting a pointer, but what you've actually got is an actual BulletObj. The dereference is not required and in fact will not compile. Also, "this" is already a pointer, taking its address results in a pointer-to-a-pointer; you'll get another compile error there. Remove the address-of operation.

Quote:
bulletInstances[currentStep].~BulletObj();

But I'm pretty sure this will get my compiler really mad.

It is legal to manually invoke the destructor (your syntax for accessing the array is, again, wrong: bulletInstances[currentStep] is a BulletObj*, to call a method on that pointer you need to use -> and not .). However, it is a bad idea to be doing this, since the destructor ends the lifetime of the object (but not the storage).

Quote:
class OBJECT_BASE
{
/*friend FUNCTION TO CHECK COLLISION*/
private:
unsigned int ID;
int x, y;
double direction;

public:
OBJECT_BASE(int Nx, int Ny, double Ndirection);
};

Is that all? That class is pretty useless. Since its data is private and it doesn't have any accessors, the subclasses cannot access the information they're supposedly supposed to be using in one form or another.


Now, the syntax issues aside: your design is bad.

  • I disagree with the need for BulletObj to derive from OBJECT_BASE (what is with the inconsistent capitalization, by the way?), especially since OBJECT_BASE doesn't really net you anything since its mostly useless.

  • You have a linear-complexity search for a new ID; you can achieve this in constant time (use a stack). Reusing IDs is questionable practice, anyway; it can lead to interesting bugs.

  • BulletObjs manage their own lifetime; they are, essentially, performing double-duty, which is bad design. If lifetime (or other) management is required, make a class that is specifically geared towards doing that. This will also allow you to encapsulate your array (should be a vector) of instances someplace nice, instead of making a global.

  • Related to the previous item, you cannot create a BulletObj without having it automatically list itself in the global array; this can be undesirable, sometimes, and you cannot work around it with the system you currently have.

Please wrap source code with more than a few lines into [ source ] and [ /source ] tags (but leave out the inner spaces besides the brakets). It would make your code more readable since it preserves indentation and provides some syntax coloring. Thx.

The declaration
BulletObj* bulletInstances[148];
means 148 pointers to instances of class BulletObj. But if using C++ you have to initialize the pointers. In your special case it may be sufficient to initialize the one pointer just behind the last used one, but it would be safer if all pointers are initialized.

The array is 148 elements big, and iCheck counts up to 404 before breaking. So the c'tor of BulletObj is buggy.

The conditional
if (*bulletInstances[iCheck] = NULL){
assigns NULL to whatever bulletInstance[] points to. I assume you wanted to check whether the pointer itself is NULL to find the first free place, so you may want to use
if (bulletInstances[iCheck] == NULL){
instead.

The line
*bulletInstances[iCheck] = &this
is missing a semicolon at its end. It furthurmore assigns a BulletObj** to what bulletInstances[iCheck] points to. I assume you wanted to assign the current object to the pointer, so you may want to use
bulletInstances[iCheck] = this;
instead.

In the d'tor the line
*bulletInstances[ID] = NULL;
should possibly be
bulletInstances[ID] = NULL;
since you want to clear the pointer in the array!?

Oh, and in
for (int currentStep = 0; currentStep < 148; currentStep++){
  bulletInstances[currentStep].step();
}
the inner line has to be
bulletInstances[currentStep]->step();


EDIT: jpetrie has covered most of this already and was faster than me, and he is IMHO right with his conclusions. That's life ;)
Ok, thanks, I'll correct these pointer mistakes. I'll also try to make a better ID system... damn, again to the drawing boards :)

ok, let's see, the weird 405 number of iteration was the previous number I got, and I probably forgot to change it every where. So yeah, it should be 148 too.

The '=' in the IF statement was also a mistake.

My strange capitalization of the OBJECT_BASE is probably over-exposure to windows constants and class names :p. Also, I use this class because I have many objects types, I also have asteroids and explosions with the same variables, and I thought it would be simpler to regroup the same members in a class and make every other classes upon this one.

I'm interested by this stack processing of IDs, but how could I achieve this? because every data structures (lists, queues, stacks) are based upon arrays, isn't it?

When I'm destroying my objects, I use a "collision detection" function, which is called just after every steps function of the objects, I'm not sure it's what you're talking about, but I think this 'should' be ok, otherwise, I don't see how another class could handle the deletion of the objects without wasting memory...

I'll get these problems corrected, and I'll try to compile it, and see the desaster...

Thanks again!

Gabry Hyrule

Oh! I forgot, could I possibly use the new and delete operators in my constructor and destructor to manage their memory with their lifetime?
I'm 15, and I know C++, OpenGL and a little Windows programming.
Quote:
ok, let's see, the weird 405 number of iteration was the previous number I got, and I probably forgot to change it every where. So yeah, it should be 148 too.

Case in point. Use std::vector.

Quote:
My strange capitalization of the OBJECT_BASE is probably over-exposure to windows constants and class names :p.

Windows, being a C API, doesn't use "class" to mean the same thing as what you get with the C++ "class" keyword. In any case, name things consistently.

Quote:
I'm interested by this stack processing of IDs, but how could I achieve this? because every data structures (lists, queues, stacks) are based upon arrays, isn't it?

Logically yes, and the SC++L stack class is a container adaptor for vector, which itself is a dynamic array. That doesn't mean that everything needs to physically use pure arrays; I've mentioned numerous times now, you should use std::vector.

Now then, consider a simple implementation:
class UniqueIDGenerator{  public:    UniqueIDGenerator()    : mNextID(0)    {    }        size_t AcquireID()    {      // If we have an ID in the reusable stack, reuse it.      if(!mReusable.empty())      {      size_t  result = mReusable.top();              mReusable.pop();        return (result);      }            // Otherwise, place the next available ID in the       // set of used IDs and return the new ID.      mUsedIDs.insert(mNextID);      return (mNextID++);    }        void ReclaimID(size_t id)    {    std::set< size_t >::iterator it = mUsedIDs.find(id);            if(it != mUsedIDs.end())      {        // Store the ID on the reusable IDs stack for        // potential reuse later on when a new ID is        // requested.        mReusable.push(*it);        mUsedIDs.erase(it);      }    }  private:    size_t                mNextID;    std::set< size_t >    mUsedIDs;    std::stack< size_t >  mReusable;};


This (very simple) implementation allows your (hypothetical) bullet management interface to acquire and reclaim unique IDs (unique per UniqueIDGenerator instance, obviously) for use with the bullets it manages. It allows for ID reuse by storing reclaimed IDs on a stack. When generating new IDs, if the stack is non-empty, the top value is popped from the stack and returned.

Note that this implementation isn't particularly exception safe, doesn't have great error handling, and allows the issues with reusable IDs to become very apparent (since IDs are reused quickly). I still maintain reusable IDs are generally a bad idea.

Quote:
When I'm destroying my objects, I use a "collision detection" function, which is called just after every steps function of the objects, I'm not sure it's what you're talking about, but I think this 'should' be ok, otherwise, I don't see how another class could handle the deletion of the objects without wasting memory...

Are you referring to calling the destructor manually? The reason this is bad is that it can leave your object in an unusable state (its lifetime has ended), but the object is still accessible (its storage is still around). In general, you should not call the destructor manually unless you really know what you are doing. You do not.

Manual destructor invocation is usually seen when storage has been decoupled from lifetime, as in when using placement new. You aren't using placement new, and you definately don't need to manually invoke the destructor.

Note especially that manually invoking it will cause "double destruction" and potentially undefined behavior, because eventually the destructor will be automatically invoked for you (if the original object was on the stack, it will be destroyed when it goes out of scope; if the original object was on the heap, it will be destroyed when delete is called; in both cases the destructor would be called a total of two times if you also manually invoked it, which is bad).

If a pointer is just aliasing another object, it does not need to be cleaned up and will not leak memory (provided the original object is cleaned up). Presumably in your case, the actual BulletObjs are created via new someplace, and bulletInstances just points at them. You clean up those BulletObjs by calling delete, not by manually invoking the destructor.

A bullet management interface should handle this for you. That interface should be able to:

  • When requested, create a new bullet object.

  • When requested, destroy a given bullet object.

  • Be able to process and update all bullets' simulation state.

  • Manage the lifetime of the actual bullet objects.


(Note the lack of anything related to rendering bullets, since its generally more appropriate to keep rendering and simulation isolated).

Quote:
Oh! I forgot, could I possibly use the new and delete operators in my constructor and destructor to manage their memory with their lifetime?

Manage the memory of what? The BulletObj itself? No. Once you are in the constructor (or destructor), your memory is already allocated (or is about to be deallocated).

I think you might need to re-read your C++ book's chapter on pointers and object lifetimes. You might also want to check out the relevant sections in The C++ FAQ Lite.
Ok, thanks for the explanation, I don't really understand it, but I really need to try simpler things before ID things... I think I'll just try another way to call every drawing.

Thanks again.

Gabry Hyrule
I'm 15, and I know C++, OpenGL and a little Windows programming.

This topic is closed to new replies.

Advertisement