В поисках лучшего способа интеграции статического списка в набор классов - PullRequest
2 голосов
/ 14 марта 2010

Я пытаюсь расширить интерес своих сыновей от программирования на Warcraft 3 к C ++, чтобы расширить его кругозор до некоторой степени. Мы планируем портировать небольшую игру, которую он написал.

Контекст выглядит примерно так:

  • Существуют корабли и ракеты, для которых корабли будут использовать ракеты и взаимодействовать с ними
  • Существует контейнер, который будет содержать «список» кораблей.
  • Существует Контейнер, который будет содержать «список» планет.
  • Можно применить функцию ко всем элементам в контейнере (for_each)
  • Корабли и Ракеты могут быть созданы / уничтожены в любое время
  • Новые объекты автоматически вставляются в нужный контейнер.

Я собрал небольшой пример, чтобы выполнить эту работу, чтобы мы могли говорить о темах (список, шаблоны и т. Д.), Но я не доволен результатами.

#include <iostream>
#include <list>
using namespace std;

/* Base class to hold static list in common with various object groups */ 
template<class T>
class ObjectManager
{
    public :
        ObjectManager(void)
        {
            cout << "Construct ObjectManager at " << this << endl;

            objectList.push_back(this);
        }

        virtual ~ObjectManager(void)
        {
            cout << "Destroy ObjectManager at " << this << endl;
        }

        void for_each(void (*function)(T *))
        {
            for (objectListIter = objectList.begin(); 
                 objectListIter != objectList.end(); 
                 ++objectListIter)
            {
                (*function)((T *) *objectListIter);
            }
        }
               list<ObjectManager<T> *>::iterator objectListIter;
        static list<ObjectManager<T> *>           objectList;
};

/* initializer for static list */ 
template<class T>
list<ObjectManager<T> *> ObjectManager<T>::objectList;


/* A simple ship for testing */ 
class Ship : public ObjectManager<Ship>
{
    public :
        Ship(void) : ObjectManager<Ship>()
        {
            cout << "Construct Ship at " << this << endl;
        }

        ~Ship(void)
        {
            cout << "Destroy Ship at " << this << endl;
        }

        friend ostream &operator<<(ostream    &out,const Ship &that)
        {
            out << "I am a ship";

            return out;
        }
};

/* A simple missile for testing */ 
class Missile : public ObjectManager<Missile>
{
    public :
        Missile(void) : ObjectManager<Missile>()
        {
            cout << "Construct Missile at " << this << endl;
        }

        ~Missile(void)
        {
            cout << "Destroy Missile at " << this << endl;
        }

        friend ostream &operator<<(ostream &out,const Missile &that)
        {
            out << "I am a missile";

            return out;
        }
};

/* A function suitable for the for_each function */ 
template <class T>
void show(T *it)
{
    cout << "Show: " << *it << " at " << it << endl;
}

int main(void)
{
    /* Create dummy planets for testing */ 
    Missile p1;
    Missile p2;

    /* Demonstrate Iterator */ 
    p1.for_each(show);

    /* Create dummy ships for testing */ 
    Ship s1;
    Ship s2;
    Ship s3;

    /* Demonstrate Iterator */ 
    s1.for_each(show);

    return 0;
}

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

Мой вопрос сводится к " Есть ли лучший способ сделать это? "

  1. Автоматическое создание контейнера объекта
  2. Автоматическая вставка объекта
  3. Доступ к контейнеру без необходимости доступа к объекту в списке.

Я ищу лучшие идеи. Все полезные записи получают upvote.

Спасибо

Evil.

Ответы [ 5 ]

1 голос
/ 19 марта 2010

В основном это типичный случай неправильного использования наследования. Ракета и Корабль вообще не должны наследоваться от ObjectManager. Вы должны зарезервировать наследование, когда у вас есть «своего рода» отношения между объектами (например, «Ядерная ракета» - это разновидность ракеты).

Здесь вы используете наследование для его «механических» свойств (автоматическая регистрация новых экземпляров), но для достижения той же цели было бы так же легко создавать объекты, говорит Ракета, из некоторого общего класса ManagedObject и заставлять его добавлять новые экземпляры некоторых уникальных глобальных объектов, управляющих списком экземпляров (или делайте это без наследования, переопределяя значения по умолчанию new и delete для Missiles). Либо вы можете передать менеджера в класс Missile в качестве параметра экземпляра, либо создать Missiles через какой-либо метод конструктора менеджера, который добавит его в контейнер.

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

Подумав о различных возможностях, я пришел к следующему коду. Пример только для корабля и не связывается с ракетой или использованием общего кода для обоих классов через шаблон (даже если это достаточно просто). Мой ответ сфокусирован на ведении актуального списка всех экземпляров Корабля, существующих в системе в данный момент времени, без вмешательства в наследство. Я выбрал это решение, потому что считаю, что оно более KISS (Keep It Stupid and Simple), а сложные решения - настоящее бремя, когда вам приходится поддерживать свой код. Идея также состоит в том, чтобы избежать перекодирования чего-то, что уже существует в STL, например, for_each.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
vector<class Ship*> all_ships;

class Ship
{
    public:
        Ship(void){
            cout << "Construct Ship at " << this << endl;
            all_ships.push_back(this);
        }

        ~Ship(void){
            cout << "Destroy Ship at " << this << std::endl;
            // we remove it from the list
            int i = 0;
            while (all_ships[i] != this) { i++; }
            all_ships[i] = all_ships.back();
            all_ships.pop_back();
        }

    static void show(Ship *s){
        cout << "ship :" << s << "\n";
    }

};


void list_all_ships(){
    cout << "List of Ships\n";
    for_each(all_ships.begin(), all_ships.end(), Ship::show);
    cout << "\n";
}

int main(void)
{
    cout << "Two automatic (on stack) Ships\n";
    Ship s1;
    Ship s2;
    list_all_ships();

    cout << "One new dynamic (on heap) Ship\n";
    Ship * s3 = new Ship();
    list_all_ships();

    cout << "Delete dynamic Ship\n";
    delete s3;
    list_all_ships();

    cout << "The two dynamic Ships will be deleted when getting out of Scope\n";
    return 0;
}
1 голос
/ 19 марта 2010

Я думаю, что проблема в том, что вы растягиваете понятие наследования. То, что у вас есть, является действительным кодом и работает, но, как вы заметили, неуместно скрывать класс контейнера за кулисами конструктора вашего дочернего класса. Это (на мой взгляд), поскольку у Missile нет ничего общего с ее родительским классом (контейнером), вы используете наследование для предоставления этого контейнера, когда это шаблон проектирования, который (правильно) был абстрагирован в стандартная библиотека шаблонов. Как полагает Джон Цвинк, шаблон Factory предоставит гораздо более выразительный способ достижения того, чего вы хотите.

/* Container class to create and store various object groups */ 
template<class T>
class ObjectManager
{
public :
    ObjectManager() : nextAvailableIndex(0)
    {}

    virtual ~ObjectManager(void)
    {
        for (objectListIter = objectList.begin(); objectListIter != objectList.end(); ++objectListIter)
            delete *objectListIter;
    }

    T* ObjectManager::Create()
    {
        T* object = new Object(nextAvailableIndex++); 
        cout << "Constructed object " << object << endl;
        objectList.push_back(object);
    }

    void for_each(void (*function)(T *))
    {
        for (objectListIter = objectList.begin(); objectListIter != objectList.end(); ++objectListIter)
        {
            (*function)((T *) *objectListIter);
        }
    }

    int                nextAvailableIndex;
    list<T*>::iterator objectListIter;
    list<T*>           objectList;
};

int main(void)
{
    /* Create dummy planets for testing */ 
    ObjectManager<Missile> missileManager;
    Missile* p1 = missileManager.Create();

    /* Demonstrate Iterator */ 
    missileManager.for_each(show);

    /* Create dummy ships for testing */ 
    ObjectManager<Ship> shipManager;
    shipManager.Create(); 
    shipManager.Create(); 
    shipManager.Create(); 

    /* Demonstrate Iterator */ 
    shipManager.for_each(show); // will show three ships

    return 0;
}

Я исключил определения классов для Missile и Ship, так как единственное изменение, которое я добавил, - это одно целое число для конструкторов, чтобы они функционировали как уникальный идентификатор.

1 голос
/ 14 марта 2010

Использовать контейнер объекта простым доступом, т.е. ObjectManager<T>::objectList.

Когда вы превращаете ObjectManager<T>::for_each в статическую функцию, ваш main может выглядеть следующим образом:

int main(void)
{
    /* Create dummies */ 
    Missile p1;
    Missile p2;
    Ship s1;
    Ship s2;
    Ship s3;

    /* Demonstrate Iterators */ 
    Missile::for_each(show); 
    Ship::for_each(show);

    /* Demonstrate Direct Container Access */ 
    Missile::objectList.pop_back();
    Missile::for_each(show); 

    return 0;
}

Кстати, это вопрос индивидуальных предпочтений, но я бы использовал std::vector вместо std::list для хранения указателей на созданные объекты, чтобы иметь произвольную доступность.

Еще одна вещь: удалить объект из списка, когда он будет уничтожен.

1 голос
/ 14 марта 2010

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

template<typename Object>
class ObjectManager {
public:
    template<class Action> void forEach(Action action); // like std::for_each
    Object& create(); // inserts into internal list
// ...
}

Я отмечу здесь, что все это кажется немного перегруженным. «ObjectManager» ... само название звучит слишком обобщенно. Но, возможно, вы пытаетесь научить некоторые вещи ООП "чистым" способом, так почему бы и нет.

0 голосов
/ 14 марта 2010

Почему бы не сделать ObjectManager<T>::for_each() статическим методом?

...