代码之家  ›  专栏  ›  技术社区  ›  buddhabrot

避免符号冲突的C语言类泛型/OO编程

c
  •  2
  • buddhabrot  · 技术社区  · 15 年前

    我正在用C做一个小游戏。我试着用函数指针以面向对象的方式编程。
    这次我真的很想继续前进,不要把东西做得太普通,我经常迷失在这件事上。使用普通的C语言帮助我更快更好地编程。

    目前,我使用以下方法描述“游戏状态”:

    /* macros */
    
    #define SETUP_ROUTINE(component) component##_##setup_routine
    #define DRAW_ROUTINE(component) component##_##draw_routine
    #define EVENT_ROUTINE(component) component##_##event_routine
    #define UPDATE_ROUTINE(component) component##_##update_routine
    #define TEARDOWN_ROUTINE(component) component##_##teardown_routine
    
    #define SETUP_ROUTINE_SIGNATURE void
    #define DRAW_ROUTINE_SIGNATURE void
    #define EVENT_ROUTINE_SIGNATURE SDL_Event evt, int * quit
    #define UPDATE_ROUTINE_SIGNATURE double t, float dt
    #define TEARDOWN_ROUTINE_SIGNATURE void
    
    /* data */
    
    typedef enum GameStateType {
        GAME_STATE_MENU,
        GAME_STATE_LEVELSELECT,
        ...
    } GameStateType;
    
    typedef struct GameState {
        GameStateType state;
        GameStateType nextState;
        GameStateType prevState;
    
        void (*setup_routine)(SETUP_ROUTINE_SIGNATURE);
        void (*draw_routine)(DRAW_ROUTINE_SIGNATURE);
        void (*event_routine)(EVENT_ROUTINE_SIGNATURE);
        void (*update_routine)(UPDATE_ROUTINE_SIGNATURE);
        void (*teardown_routine)(TEARDOWN_ROUTINE_SIGNATURE);
    
    } GameState;
    

    虽然你可能喜欢也可能不喜欢这种风格,但我已经开始喜欢它了,到目前为止,它对我这个小型(私人)项目很有帮助。

    例如,我有一个“过渡”游戏状态,它只是从一个游戏状态过渡到另一个。

    然而,当我将不同的游戏状态链接在一起时,我会得到一些丑陋的东西,比如:

    extern GameState GAME; /* The 'singleton' "game" */
    
    extern void menu_setup_routine(SETUP_ROUTINE_SIGNATURE);
    extern void menu_draw_routine(DRAW_ROUTINE_SIGNATURE);
    extern void menu_event_routine(EVENT_ROUTINE_SIGNATURE);
    extern void menu_update_routine(UPDATE_ROUTINE_SIGNATURE);
    extern void menu_teardown_routine(TEARDOWN_ROUTINE_SIGNATURE);
    
    extern void debug_setup_routine(SETUP_ROUTINE_SIGNATURE);
    extern void debug_draw_routine(DRAW_ROUTINE_SIGNATURE);
    extern void debug_event_routine(EVENT_ROUTINE_SIGNATURE);
    extern void debug_update_routine(UPDATE_ROUTINE_SIGNATURE);
    extern void debug_teardown_routine(TEARDOWN_ROUTINE_SIGNATURE);
    

    另外,对于每个游戏状态,我都有这样的东西:

    菜单c

    struct MenuModel menu_model; /* The singleton 'menu' model */
    

    游戏c

    struct GameModel game_model; /* The singleton 'game' model */
    

    …它们是在整个程序执行过程中留在堆中的全局数据块。当然,这些字段通常由指向动态内存的指针组成,动态内存和动态内存的内容随着游戏状态的变化而变化。
    一开始我觉得这很疯狂,我开始喜欢它。但是,当另一个.o链接时,如果它也有这样一个“menu_model”符号,则可能会导致命名空间冲突。

    第一个问题:这是不是疯了,有没有更好的方法来做这样的事情?人们通常做些什么来避免这些可能的符号名冲突?

    第二个问题是我必须重新发布不同的…\u setup\u routine/。draw\u routine/。。在一个包含以下类型函数的源文件/对象文件中使用“extern..”的函数:

    void (*get_setup_routine(GameStateType state))(SETUP_ROUTINE_SIGNATURE) {
        switch(state) {
            case GAME_STATE_MENU:
                return SETUP_ROUTINE(menu);
                break;
            case GAME_STATE_LEVELSELECT:
                return SETUP_ROUTINE(level_select);
                break;
            default: /* ... */ break;
        } 
    }
    

    因为否则编译时它不知道符号“menu\u setup\u routine”。

    无论如何,任何建议都是受欢迎的,我对C有点陌生,虽然我真的很喜欢在其中编程,但我想知道在这种情况下我是否正确使用了它。

    2 回复  |  直到 15 年前
        1
  •  2
  •   peoro    15 年前

    一些非小游戏使用类似的 范式 . 我脑海中浮现的第一个例子是 Neverball .
    你可能想下载它的源代码(这是一个开源游戏),看看他们是怎么做的。

    我个人认为你应该检查C++。我过去只使用C,也在使用你的方式,直到几年前,然后我疯了(主要是因为名字冲突),切换到C++让我发现了一个新的世界。不管怎样,我知道你可能有很多原因想避免它。


    关于像你这样的人 menu_model ,其名称与其他名称冲突 菜单模式 在其他C源文件中,您应该将它们声明为 static :

    static struct MenuModel menu_model; /* The singleton 'menu' model */
    

    那个 菜单模式 将在其声明的C源文件中可见(您将无法在其他C源文件中使用它,甚至不能在 extern 它的名字不会和其他的名字冲突 静止的 其他C源文件中声明的同名变量。


    关于第二个问题没什么可做的。必须声明您使用的函数和变量。

        2
  •  2
  •   YeahStu    15 年前

    我有点困惑,但我不认为你需要所有这些 menu_setup_routine 等等都有外部联系。相反,定义 struct game_vtable 为每个例程包含一个函数指针,然后让“menu”和“debug”中的每一个都提供对该结构实例的访问。要对组件调用函数,可以执行以下操作:

    // vtable is a global symbol
    component##_##vtable.setup
    

    // vtable is acquired from a function
    component##_##getvtableptr()->setup
    

    或者您可以将vtable指针作为参数传递,以代替GameStateType,并可能因此删除一些switch语句。

    至于全局菜单——你没有提供太多细节,但是避免全局菜单的方法是在本地创建一个高级别的菜单,然后将其传递给任何需要它的人。如果你决定你更喜欢全局,你必须给它一个唯一的名字,如果它要在它的TU之外可见。