Я могу понять, как void**
может выглядеть в памяти, но мне интересно, правильно ли я его использую. Есть ли фундаментальные недостатки в том, что я опишу ниже? Например, хотя я могу сказать «это работает для меня», я каким-то образом создаю плохой / непортативный код?
Итак, у меня есть клон астероидов. Есть три сущности, которые могут стрелять пулями, игроки (SHIP *player_1
, SHIP *player_2
) и НЛО (UFO *ufo
). Когда стреляет пуля, важно знать, кто выстрелил; если это был игрок, когда он что-то поражает, их счет должен быть увеличен. Таким образом, пуля будет хранить, к какому типу сущности она принадлежит (owner_type
), а также указатель непосредственно на владельца (owner
):
enum ShipType
{
SHIP_PLAYER,
SHIP_UFO
};
typedef struct Bullet
{
// ...other properties
enum ShipType owner_type;
void **owner;
} BULLET;
Затем, когда игрок нажимает кнопку или НЛО видит цель, будет вызвана одна из этих функций:
void ship_fire(SHIP **shipp)
{
BULLET *bullet = calloc(1, sizeof(BULLET));
bullet->owner_type = SHIP_PLAYER;
bullet->owner = (void**)shipp;
// do other things
}
void ufo_fire(UFO **ufop)
{
BULLET *bullet = calloc(1, sizeof(BULLET));
bullet->owner_type = SHIP_UFO;
bullet->owner = (void**)ufop;
// do other things
}
... их можно назвать, например, так:
ship_fire(&player_1);
Наконец, когда пуля попадает в цель (например, астероид), мы разыменовываем владельца. Если это корабль, мы можем тут же увеличить счет.
void hit_asteroid(ASTEROID *ast, BULLET *bullet)
{
SHIP *ship_owner;
if (bullet->owner_type == SHIP_PLAYER && *bullet->owner != NULL)
{
ship_owner = (SHIP*)*bullet->owner;
ship_owner->score += 1000;
}
}
Это кажется разумным подходом? Как я уже сказал, это работает для меня, но у меня есть только несколько месяцев опыта работы с C.
Последнее замечание: почему я не использую void*
вместо void**
? Потому что я хочу избежать висящих указателей. Другими словами, скажите, что player_1
умирает и свободен, но их пуля продолжает двигаться и попадает в астероид. Если у меня есть только void*
, функция hit_asteroid
не может знать, что bullet->owner
указывает на нераспределенную память. Но с void**
я могу достоверно проверить, имеет ли он значение NULL; если player_1
равно NULL, то *bullet->owner
также будет равно NULL.
РЕДАКТИРОВАТЬ: Все респонденты до сих пор согласны с тем, что использование void **, вероятно, не является необходимым здесь, потому что я могу избежать проблемы висячих указателей (например, просто статическое размещение базового объекта). Они верны, и я сделаю рефакторинг. Но мне все еще интересно узнать, использовал ли я void ** таким образом, что мог бы что-то сломать, например. с точки зрения выделения памяти / приведения. Но я думаю, что если никто не поднял руки в воздух и не заявил, что это неисправно, то это, по крайней мере, похоже на то, что технически сработает.
Спасибо!