Можете ли вы привести указатель void к структуре для определенного типа структуры во время выполнения?

У меня есть структура, определенная следующим образом:

struct GameState {
    int score;
    int moves;
    bool won;

    void *metadata;
};
typedef struct GameState GameState;

Указатель метаданных будет указывать на другую структуру типа, определенного во время выполнения. Например, это может быть:

struct KlondikeMetadata{
    bool draw3;
    int drawcount;
};
typedef struct KlondikeMetadata KlondikeMetadata;

Или, может быть:

struct FreeCellMetadata{
    int reserveCells;
};
typedef struct FreeCellMetadata FreeCellMetadata;

Реальная используемая структура метаданных зависит от игры, в которую играет пользователь. В 99% случаев это не проблема, потому что я знаю, в какую игру играет пользователь. Однако бывают случаи, когда я не знаю (и не могу) этого знать.

Мой вопрос: есть ли способ определить или указать правильный тип метаданных во время выполнения?

Например, если бы я мог добавить свойство в структуру GameState, указывающее, что значение метаданных имеет тип KlondikeMetadata, и использовать его для приведения метаданных к этому типу, я думаю, что я был бы золотым. Есть ли способ сделать это? Есть ли способ указать тип и привести переменную во время выполнения в C?


person Doug Hughes    schedule 19.03.2013    source источник
comment
Мне просто пришло в голову, что я мог бы просто иметь значение, чтобы указать тип структуры 1:KlondikeMetadata, 2:FreeCellMetadata. И тогда я мог бы включить это значение и в каждом случае привести (со статическим кодом) к правильному типу.   -  person Doug Hughes    schedule 19.03.2013
comment
Если это только один из двух хорошо известных типов, union может быть лучше, чем использование void * плюс приведения. Это требует такой же бухгалтерии, конечно.   -  person FatalError    schedule 19.03.2013
comment
Интересно. Раньше я не знал о профсоюзах. Спасибо. В конце концов (надеюсь) будут десятки этих структур метаданных. В любом случае (союз или пустота) кажется, что объем работы примерно одинаков.   -  person Doug Hughes    schedule 19.03.2013


Ответы (1)


Вам придется кодировать его самостоятельно.

Самое простое решение - объявить перечисление:

typedef enum {
 GameType_Klondike,
 GameType_FreeCell,
} GameType;

затем добавьте поле этого типа перед указателем:

GameType game_type;
void     *metadata;

Конечно, это означает, что вам придется установить поле game_type при инициализации metadata, чтобы запомнить тип.

Вы также можете пойти немного объектно-ориентированно и сделать GameType частью метаданных:

struct Metadata {
 GameType gametype;
};

struct FreeCellMetadata {
 struct Metadata meta;
 /* rest of fields here */
};

struct KlondikeMetadata {
 struct Metadata meta;
 /* rest of fields here */
};

Затем вы можете преобразовать void * в struct Metadata * и проверить поле gametype перед приведением указателя к нужному типу.

Для бонусных баллов используйте союз:

struct Metadata {
 GameType type;
 union {
 struct KlondikeMetadata klondike;
 struct FreecellMetadata freecellL;
 } game;
};

Опять же, конечно, это требует, чтобы вы поддерживали данные, т. е. когда вы инициализируете struct KlondikeMetadata, вы должны не забыть установить его поле gametype и так далее.

person unwind    schedule 19.03.2013
comment
Я только что подумал об этом, на самом деле. Вы сказали это гораздо более красноречиво. Спасибо! - person Doug Hughes; 19.03.2013