限制包含在C++中

Limiting includes in C++

本文关键字:C++ 包含      更新时间:2023-10-16

在我的新手C++项目中,我遇到了各种各样的include重载问题,但我不知道如何避免。

如何避免必须包含数十个类的问题,例如在地图加载场景中:

下面是一个简单的Map类示例,它将从以下文件加载游戏地图:

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

现在假设我有大量的怪物要加载到我的地图中,所以我可能有一个列表或其他结构来保存地图中所有的怪物定义

std::list<CMonster*> MonsterList;

然后,我可以在我的CMap.h中简单地向前声明"CMonster",并将我喜欢的任意数量的怪物添加到列表中

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

但如果我有很多不同类型的怪物呢?如何在不包括每个CMonster_XXX.h的情况下创建许多不同类型的怪物?还使用这些方法?

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

以下是我用于包含事物的规则。

  • 在头文件中尽可能多地转发声明
  • 在.cpp中包含您需要的任何.h
  • 除非迫不得已,否则不要将.h包含在其他.h中
  • 如果您的项目构建不需要包含.h,那么您就可以了。(大多数情况下,前提是编译器足够兼容)

编辑:此外,你可能想阅读大规模C++软件设计。它谈到了管理物理文件依赖关系。

您可以创建一个类似的文件myMonstersInclude.h

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

您的主代码只需要执行`#include"myMonstersInclude.h"。

您甚至可以使用构建工具生成它,大多数工具都允许您在每一步之前和之后运行自己的脚本。

简短的回答是:你不能。

稍长的是:您可以创建一个仅#包含其他头文件的头文件,并将新的头文件包含在.cpp文件中。尽管如此,这仍然有效地包括了所有的标题,你只是没有重复的包含列表,这就是为什么我说简短的答案是你不能。

新标题类似于:

#include CMonster_cat
#include CMonster_puppy
...

问题是,你的地图真的需要了解所有类型的怪物吗?可能不会——就地图而言,只知道它们源自CMonster就足够了。地图类使用的所有方法都应该能够通过怪物上的虚拟函数进行操作,因此每个怪物类型都定义了其专门的行为,而不是地图。

我怀疑,通过正确利用这里的继承,你的"包含问题"会大大减少。

您可以使用工厂函数。与全局静态对象组合以注册类型。类似这样的东西:

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

我们添加一些粘合代码来注册创建函数。。。

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

最后,回到主文件中,您可以根据其类型的名称创建一个怪物对象。。。

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

以下是发生的情况:

当程序启动时,在main之前,它将运行全局对象的所有构造函数,在这种情况下,register_monsterA就是其中之一。

这个对象的构造函数将获得get_map()(它不能只是一个全局static,因为如果我们这样做,我们无法知道初始化的顺序,所以它是一个函数)。

然后它会添加一个项目,这是一个"创建函数",基本上是一个能够制作新CMonster的函数。

最后,要制作一个怪物,我们只需在同一张地图中查看,然后获得创建函数并运行它(如果它存在的话)。


编辑:下面是一个完整的工作示例。。。(用一些宏魔术让它更干净)

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