0

I am having all sorts of problems with include-overload in my newbie C++ project, but I'm not sure how to avoid it.

How do I avoid the problem of having to include dozens of classes, for example in a map-loading scenario:

Here's a trivial example Map class, which will load a game-map from a file:

// CMap.h
#ifndef _CMAP_H_
#define _CMAP_H_
class CMap {
    public:
        CMap();
        void OnLoad();
};
#endif

// CMap.cpp
#include "CMap.h"
CMap::CMap() {
}

void CMap::OnLoad() {
    // read a big file with all the map definitions in it here
}

Now let's say I have a whole plethora of monsters to load into my map, so I might have a list or some other structure to hold all my monster definitions in the map

std::list<CMonster*> MonsterList;

Then I could simple forward-declare "CMonster" in my CMap.h, and add as many monsters as I like to that list

// CMap.h
class CMonster;

// CMap.cpp
void CMap::OnLoad() {
    // read a big file with all the map definitions in it here
    // ...
    // read in a bunch of mobs
    CMonster* monster;
    MonsterList.push_back(monster);
}

But what if I have lots of different types of monster? How do I create lots of different types of monster without including every CMonster_XXX.h? And also use methods on those?

// CMap.cpp
void CMap::OnLoad() {
    // read a big file with all the map definitions in it here
    // ...
    // read in a bunch of mobs
    CMonster_Kitten* kitty;
    kitty->OnLoad();
    MonsterList.push_back(kitty);

    CMonster_Puppy *puppy;
    puppy->OnLoad();
    puppy->SetPrey(kitty);
    MonsterList.push_back(puppy);

    CMonster_TRex *awesome;
    awesome->OnLoad();
    awesome->SetPrey(puppy);
    MonsterList.push_back(awesome);
}
Marc Butler
  • 1,346
  • 7
  • 13
Cylindric
  • 5,858
  • 5
  • 46
  • 68
  • Your `OnLoad` definition is inconsistent (and contains syntax errors). Does it actually contain the entire map definition or does it just load it from file? That matters, since a file loader might need much less knowledge of what it's initializing. – Fred Foo Mar 08 '12 at 23:12
  • I think the Abstract Factory Pattern may be appropriate. However, it has been some time since I did any serious object-disoriented programming. It might let you aggregate all those headers into a single compilation unit ".cpp", which will let you isolate the `#include` nest. Regardless, you might want to research the "Builder Patterns" which are concerned with these kind of situations. – Marc Butler Mar 08 '12 at 23:17

5 Answers5

3

Here's the rule I use for including things.

  • Forward declare as much as you can in your header files.
  • include any .h you need in your .cpp
  • don't include .h in other .h unless you have to.
  • If your project build without needing to include a .h, you are fine. (mostly, provided your compiler is compliant enough)

Edit: Additionally, you may want to read Large-Scale C++ Software Design. It talks about managing physical file dependencies.

Camford
  • 770
  • 3
  • 5
  • i second the Lakos book recommendation. i'm glad to see that the Lakos book has now made it to the number 3 spot in the answers to this thread: http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list – pestophagous Apr 24 '13 at 22:42
2

You could create a file myMonstersInclude.h like

#include "myMonster1.h"
#include "myMonster2.h"
....

Your main code will only need to do `#include "myMonstersInclude.h".

You could even generate it using your build tools, most allow you to run your own script before and after every step.

SJuan76
  • 24,532
  • 6
  • 47
  • 87
0

Short answer is: You can't.

Slightly longer is: You can create a header file that just #includes the other ones, and include the new header file in your .cpp files. This is still effectively including all the headers though, you just don't have the list of includes duplicated, which is why I said the shorts answer is you can't.

The new header would something like:

#include CMonster_cat
#include CMonster_puppy
...
Christian Horsdal
  • 4,914
  • 23
  • 24
0

The question is, does your map really need to know about all the individual types of monsters? Probably not - just knowing that they derive from CMonster should be enough as far as the map is concerned. All the methods that your map class uses should be able to operate through virtual functions on the monsters, so each monster type defines its specialized behavior, not the map.

I suspect your "include problem" will be greatly reduced by making proper use of inheritance here.

tmpearce
  • 12,523
  • 4
  • 42
  • 60
  • What I'm stuck on, is how do I ever get the 'real' type into the system? If my map uses a generic `CMonster* kitten;` so I don't need any extra includes, how do I ever declare that kitten is actually a CMonster_Kitten, and not a CMonster? – Cylindric Mar 09 '12 at 00:05
  • @Cylindric: see my answer, it demonstrates exactly how to do this without needing to include every Monster file in main factory code. Instead each Monster register's itself... – Evan Teran Mar 09 '12 at 00:21
  • @Cylindric *Somewhere* you need to have the definitions (i.e. the includes, if you have every subclass in its own .h file) - the trick is to *limit* where you include things to a minimum of places. The factory pattern that others have mentioned does just that - you have to include stuff in the factory but nowhere else (unless you require direct access to the derived classes rather than relying on the interface provided through the base class). – tmpearce Mar 09 '12 at 00:39
0

You could use a factory function. Combined with global static objects to register the types. Something like this:

// in some main file...
typedef CMonster*(*create_ptr)();

std::map<std::string, create_ptr> &get_map() {
    // so we can make sure this exists...
    // NOTE: we return a reference to this static object
    static std::map<std::string, create_ptr> map;
    return map;
}

we add some glue code to register a creation function...

// in each type of monster class (ex: CMonsterA)
CMonster *create_monster_a() {
    return new CMonsterA;
}

static struct monsterA_Registrar {
    monsterA_Registrar() {
        get_map().insert(std::make_pair("MonsterA", create_monster_a));
    }
} register_monsterA;

finally, back in the main file, you can create a monster object by the name of it's type...

std::map<std::string, create_ptr>::iterator it = get_map().find("MonsterA");
if(it != get_map().end()) {
    return (it->second)();
}

throw "invalid monster type requested";

Here's what is happening:

When the program starts, before main, it will run all constructors of global objects, in this case register_monsterA is one of them.

This object's constructor will get get_map() (it can't just be a global static because we have no way of knowing what order things get initialized in if we do, so it's a function).

Then it will add an item to it which is a "creation function", basically a function which is capable of making a new CMonster.

Finally, to make a monster, we just look in that same map, and get the creation function and run it (if it was present).


EDIT: Here's a complete working example... (with some macro magic to make it cleaner)

CMonster.h

class CMonster {
public:
    virtual ~CMonster() {
    }

    virtual void roar() = 0;
};

typedef CMonster*(*create_ptr)();

std::map<std::string, create_ptr> &get_map();

#define MONSTER_REGISTRAR(name) \
CMonster *create_monster_##name() { \
    return new C##name; \
}\
 \
static struct monster##name##_Registrar {\
    monster##name##_Registrar() { \
        get_map().insert(std::make_pair(#name, create_monster_##name));\
    } \
} register_monster##name;

CMonster.cc

std::map<std::string, create_ptr> &get_map() {
    // so we can make sure this exists...
    // NOTE: we return a reference to this static object
    static std::map<std::string, create_ptr> map;
    return map;
}

CMonsterA.cc

#include "CMonster.h"
class CMonsterA : public CMonster {
public:
    CMonsterA() {
        std::cout << "HERE - A" << std::endl;
    }
    virtual void roar() {
        std::cout << "A" << std::endl;
    }
};

MONSTER_REGISTRAR(MonsterA)

CMonsterB.cc

#include "CMonster.h"
class CMonsterB : public CMonster {
public:

    CMonsterB() {
        std::cout << "HERE - B" << std::endl;
    }
    virtual void roar() {
        std::cout << "B" << std::endl;
    }
};

MONSTER_REGISTRAR(MonsterB)

main.cc

#include "CMonster.h"

CMonster *get_monster(const std::string &name) {
    std::map<std::string, create_ptr>::iterator it = get_map().find(name);
    if(it != get_map().end()) {
        return (it->second)();
    }

    throw "invalid monster type requested";
}

int main() {

    CMonster *monster = get_monster("MonsterB");
    monster->roar();
    delete monster;
}
Evan Teran
  • 87,561
  • 32
  • 179
  • 238