Гибкие альтернативы многим многим маленьким полиморфным классам (для использования в качестве свойств, сообщений или событий) C ++


9

В моей игре есть два класса, которые действительно полезны, но постепенно становятся болью. Сообщение и свойство (свойство по сути является компонентом).

Они оба являются производными от базового класса и содержат статический идентификатор, поэтому системы могут обращать внимание только на те, которые им нужны. Это работает очень хорошо ... кроме ...

Я постоянно создаю новые типы сообщений и типов свойств по мере расширения своей игры. Каждый раз мне нужно написать 2 файла (hpp и cpp) и кучу шаблонов, чтобы получить по существу classID и один или два стандартных типа данных или указатель.

Это начинает превращать игру и проверять новые идеи в настоящую рутинную работу. Я хочу, когда я хочу создать новое сообщение или тип свойства, я хочу иметь возможность просто напечатать что-то вроде

ShootableProperty:  int gunType, float shotspeed;

ItemCollectedMessage:  int itemType;

вместо создания заголовка и файла cpp, написания конструктора, включая родительский класс и т. д. и т. д.

Это примерно 20-40 строк (включая охранников и все остальное), просто чтобы сделать то, что логически - 1 или 2 строки в моем уме.

Есть ли какой-то шаблон программирования, чтобы обойти это?

Как насчет сценариев (о которых я ничего не знаю) ... есть ли способ определить группу классов, которые почти одинаковы?


Вот как выглядит один класс:

// Velocity.h

#ifndef VELOCITY_H_
#define VELOCITY_H_

#include "Properties.h"

#include <SFML/System/Vector2.hpp>

namespace LaB
{
namespace P
{

class Velocity: public LaB::Property
{
public:
    static const PropertyID id;

    Velocity(float vx = 0.0, float vy = 0.0)
    : velocity(vx,vy) {}
    Velocity(sf::Vector2f v) : velocity(v) {};

    sf::Vector2f velocity;
};

} /* namespace P */
} /* namespace LaB */
#endif /* LaB::P_VELOCITY_H_ */



// Velocity.cpp

#include "Properties/Velocity.h"

namespace LaB
{
namespace P
{

const PropertyID Velocity::id = Property::registerID();

} /* namespace P */
} /* namespace LaB */

Все это только для 2D-вектора и идентификатора, говорящего, что он ожидает 2D-вектор. (Конечно, некоторые свойства имеют более сложные элементы, но это та же идея)


3
Посмотрите на это, это может помочь вам. gameprogrammingpatterns.com/type-object.html

1
Все это похоже на то, что шаблоны могут помочь, но только не при статической инициализации. Конечно, в этом случае сделать это намного проще, но без дополнительной информации о том, что вы хотите делать с этими идентификаторами и почему вообще используете наследование, сложно сказать. Использование публичного наследования, но без виртуальных функций - это определенно запах кода ... Это не полиморфизм!
ltjax

Вы нашли стороннюю библиотеку, которая ее реализует?
Борис

Ответы:


1

С ++ мощный, но многословный . Если вам нужно множество маленьких полиморфных классов, которые отличаются друг от друга, тогда да, для объявления и определения потребуется много исходного кода. Ничего не поделаешь, правда.

Теперь, как сказал ltjax, то, что вы здесь делаете, не совсем полиморфизм , по крайней мере для кода, который вы предоставили. Я не вижу общего интерфейса, который бы скрывал конкретную реализацию подклассов. За исключением, может быть, для идентификатора класса, но это на самом деле избыточно с реальным идентификатором класса: это имя. Кажется, это просто набор классов, содержащих некоторые данные, ничего сложного.

Но это не решает вашу проблему: вы хотите создавать множество сообщений и свойств с наименьшим количеством кода. Меньше кода означает меньше ошибок, поэтому это мудрое решение. К сожалению для вас нет единого решения. Трудно сказать, что больше всего соответствует вашим потребностям, не зная точно, что вы намерены делать с этими сообщениями и свойствами . Итак, позвольте мне представить ваши варианты:

  1. Используйте шаблоны C ++ . Шаблоны - отличный способ позволить компилятору «писать код» для вас. Это становится все более и более заметным в мире C ++, так как язык развивается, чтобы лучше их поддерживать (например, теперь вы можете использовать переменное количество параметров шаблона ). Существует целая дисциплина, посвященная автоматической генерации кода с помощью шаблонов: шаблонное метапрограммирование . Проблема в том, что это сложно. Не ожидайте, что непрограммисты смогут сами добавлять новые свойства, если вы планируете использовать такую ​​технику.

  2. Используйте простые старые макросы Си . Это старая школа, легко злоупотреблять и подвержена ошибкам. Они также очень просты в создании. Макросы - это просто прославленные копии, выполняемые препроцессором, поэтому они на самом деле вполне подходят для создания множества вещей, которые почти одинаковы с небольшими вариациями. Но не ждите, что другие программисты будут любить вас за их использование, поскольку макросы часто используются, чтобы скрыть недостатки в общем дизайне программы. Иногда, однако, они полезны.

  3. Другой вариант - использовать внешний инструмент для генерации кода для вас. Это уже упоминалось в предыдущем ответе, поэтому я не буду останавливаться на этом.

  4. Есть и другие языки, которые менее многословны и позволят вам создавать классы намного проще. Так что, если у вас уже есть привязки скрипта (или вы планируете их иметь), определение этих классов в скрипте может быть вариантом. Доступ к ним из C ++ будет довольно сложным, и вы потеряете большинство преимуществ упрощенного создания классов. Так что это возможно только в том случае, если вы планируете выполнять большую часть игровой логики на другом языке.

  5. Наконец, последнее, но не менее важное, вы должны рассмотреть возможность использования дизайна, управляемого данными . Вы можете определить эти «классы» в простых текстовых файлах, используя макет, подобный тому, который вы предложили сами. Вам придется создать собственный формат и парсер для него или использовать один из нескольких уже доступных вариантов (.ini, XML, JSON и еще много чего). Затем на стороне C ++ вам нужно будет создать систему, которая поддерживает коллекцию этих различных типов объектов. Это почти эквивалентно подходу сценариев (и, вероятно, требует еще больше работы), за исключением того, что вы сможете адаптировать его более точно к вашим потребностям. И если вы сделаете это достаточно просто, непрограммисты смогут создавать новые вещи самостоятельно.


0

Как насчет инструмента генерации кода, чтобы помочь вам?

Например, вы можете определить свой тип сообщения и членов в небольшом текстовом файле, и инструмент code gen проанализирует его и запишет все стандартные файлы C ++ в качестве шага перед сборкой.

Существуют такие решения, как ANTLR или LEX / YACC, и вам будет нетрудно накатить свои собственные в зависимости от сложности ваших свойств и сообщений.


Просто альтернатива LEX / YACC-подходу, когда мне нужно сгенерировать очень похожие файлы с небольшими изменениями, я использую простой и маленький скрипт на python и файл шаблона кода C ++, содержащий теги. Сценарий python ищет эти теги в шаблоне, заменяя их репрезентативными токенами создаваемого элемента.
Виктор

0

Я советую вам ознакомиться с буфером протокола Google ( http://code.google.com/p/protobuf/ ). Это очень умный способ обработки общих сообщений. Вам просто нужно указать свойства в шаблоне struct-llike, а генератор кода сделает всю работу по генерации классов за вас (Java, C ++ или C #). Все сгенерированные классы имеют текстовые и двоичные парсеры, что делает их пригодными как для инициализации текстовых сообщений, так и для сериализации.


Моя центральная система обмена сообщениями является ядром моей программы - знаете ли вы, что буферы протокола вызывают большие накладные расходы?
Брайан

Я не думаю, что они несут большие накладные расходы, поскольку API - это просто класс Builder для каждого сообщения и класс для самого сообщения. Google использует их для ядра своей инфраструктуры. Например, все сущности Google App Engine перед сохранением преобразуются в буфер протокола. Если вы сомневаетесь, я советую вам выполнить сравнительный тест между вашей текущей реализацией и буферами протокола. Если вы считаете, что накладные расходы приемлемы, используйте их.
dsilva.vinicius,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.