Cylindric
Cylindric

Reputation: 5894

Limiting includes in C++

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);
}

Upvotes: 0

Views: 239

Answers (5)

Evan Teran
Evan Teran

Reputation: 90462

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;
}

Upvotes: 0

tmpearce
tmpearce

Reputation: 12693

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.

Upvotes: 0

Christian Horsdal
Christian Horsdal

Reputation: 4932

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
...

Upvotes: 0

SJuan76
SJuan76

Reputation: 24885

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.

Upvotes: 2

Camford
Camford

Reputation: 780

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.

Upvotes: 3

Related Questions