jtst
jtst

Reputation: 312

How do I define C++ classes with circular dependencies?

I'm trying to set up a basic entity/object management system, and I have two classes, one for being a base-class for entities from which to inherit, and one to manage and control them all.

This is the source code I'm trying to use:

#include <iostream>
#define MAX_ENTS 400
class EFentity;
class EFiterator;
class EFentity {
     public:
          EFentity();
          virtual bool step();
          virtual void create(EFiterator*,int);
          virtual void destroy();
     private:
          int holder_id;
          EFiterator* holder;
};
EFentity::EFentity(void) {
     //    add base entity stuff
}
void EFentity::destroy() {
     holder->ents[holder_id]=NULL;
     std::cout << "destroying object id "<<holder_id;
     delete this;
}
void EFentity::create(EFiterator* h,int pos) {
     holder=h;
     holder_id=pos;
}
bool EFentity::step() {
     return false;
}
class EFiterator {
public:
     EFentity* ents[MAX_ENTS];
     int e_size;
     EFiterator();
     void push(EFentity* e);
     void update();
};
EFiterator::EFiterator() {
     e_size=0;
}
void EFiterator::update() {
     for(int i=0;i<e_size;i++) {
          if (!ents[i]->step()) {
               std::cout << "entity id "<< i<<" generated a fault!\n";
          } else std::cout << "entity id "<<i<<" passed step test.\n";
     }
}
void EFiterator::push(EFentity* e) {
     ents[e_size]=e;
     e->create(this,e_size++);
}
int main() {
     EFiterator main_iterator;
     main_iterator.push(new EFentity());
     main_iterator.update();
     std::cin.get();
     return 0;
}

This code obviously doesn't compile, and here are the errors:

In member function `virtual void EFentity::destroy()':

[20] invalid use of undefined type `struct EFiterator' 
[5] forward declaration of `struct EFiterator'

I have seen problems like this on SO before, but they did not require access to member variables and functions of the other class, so it could be easily solved with a pointer.

I think this can be solved by having a prototyped function to access the array inside of EFiterator, but is there a way to do this smoothly with some tricky class manipulation?

Upvotes: 3

Views: 261

Answers (2)

seh
seh

Reputation: 15269

The problem is that you've forward-declared type EFIterator and then tried to access its members before the definition is visible to the compiler. When the compiler reads the definition for EFentity::destroy(), it needs to know that EFIterator has a member called ents.

The solution is easy to reach: Put the two declarations ahead of the definitions, like so:

#include <iostream>
#define MAX_ENTS 400
class EFentity;
// Now we can refer to EFentity by pointer or reference.

class EFiterator {
public:
  EFentity* ents[MAX_ENTS];
  int e_size;
  EFiterator();
  void push(EFentity* e);
  void update();
};
// Now we can refer to EFiterator by pointer, reference, or value.

class EFentity {
public:
  EFentity();
  virtual bool step();
  virtual void create(EFiterator*,int);
  virtual void destroy();
private:
  int holder_id;
  EFiterator* holder;
};
// Now we can refer to EFentity by pointer, reference, or value.

EFiterator::EFiterator() {
  // ...

Now the declaration for EFiterator can reference type EFentity as a pointer (not needing to know anything else other than that it's a UDT), and the declaration for EFentity can refer to type EFiterator as a pointer (also not needing to know anything other than that EFiterator is a UDT). The order of the EFiterator and EFentity declarations doesn't matter; you could just as easily invert them like this:

#include <iostream>
#define MAX_ENTS 400
class EFiterator;
// Now we can refer to EFiterator by pointer or reference.

class EFentity {
public:
  EFentity();
  virtual bool step();
  virtual void create(EFiterator*, int);
  virtual void destroy();
private:
  int holder_id;
  EFiterator* holder;
};
// Now we can refer to EFentity by pointer, reference, or value.

class EFiterator {
public:
  EFentity* ents[MAX_ENTS];
  int e_size;
  EFiterator();
  void push(EFentity*);
  void update();
};
// Now we can refer to EFiterator by pointer, reference, or value.

EFiterator::EFiterator() {
  // ...

Not until you get to the definitions do you need to have the preceding declarations available.

Upvotes: 1

billz
billz

Reputation: 45450

EFentity::destroy() needs know concrete type of EFiterator when it calls

 holder->ents[holder_id]=NULL;

Put EFentity::destroy() after EFiterator definition should resolve the problem, for example, put it after EFiterator

void EFiterator::push(EFentity* e) {
     ents[e_size]=e;
     e->create(this,e_size++);
}

void EFentity::destroy() {
     holder->ents[holder_id]=NULL;
     std::cout << "destroying object id "<<holder_id;
     delete this;
}

Normally forward declare types in header file and include concrete type in .cpp file, which breaks circular include issue.

EFentity.h

class EFiterator;
class EFentity {
     public:
          EFentity();
          virtual bool step();
          virtual void create(EFiterator*,int);
          virtual void destroy();
     private:
          int holder_id;
          EFiterator* holder;
};

EFentity.cpp

#include "EFiterator.h"

EFentity::EFentity(void) {
     //    add base entity stuff
}
void EFentity::destroy() {
     holder->ents[holder_id]=NULL;
     std::cout << "destroying object id "<<holder_id;
     delete this;
}
void EFentity::create(EFiterator* h,int pos) {
     holder=h;
     holder_id=pos;
}
bool EFentity::step() {
     return false;
}

Upvotes: 3

Related Questions