【问题标题】:Looking for a better way to integrate a static list into a set of classes寻找将静态列表集成到一组类中的更好方法
【发布时间】:2010-03-14 14:00:25
【问题描述】:

我正在尝试将我儿子的兴趣从魔兽争霸 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. 无需列表中的对象即可访问容器。

我正在寻找更好的想法。 所有有用的条目都会获得赞成票。

谢谢

邪恶。

【问题讨论】:

  • 我不认为只有我一个人会发现您的函数参数声明样式不可读。一般来说,尤其是在这里发帖时,尽量减少垂直空白是个好主意,
  • 我不确定你想要完成什么。您的儿子在 C++ 方面有多少经验?我建议从一些非常简单且很少有自动魔法的东西开始。
  • 为什么不教他一门简单的语言呢?如果你想让他对编程感兴趣,不要把时间浪费在让一切变得比必要更难的语言上。
  • @jalf&&fabrizioM 他已经用 jass 编写了魔兽争霸 3。他非常喜欢 C++。
  • 不要使用void 表示C++ 中没有参数。只需使用空括号。

标签: c++ static-methods


【解决方案1】:

我认为这里的问题是你在扩展继承的概念。您拥有的是有效的代码并且可以工作,但是正如您所指出的,将容器类隐藏在子类的构造函数的幕后是不正确的。这是(在我看来)因为 Missile 与其父类(容器)没有任何共同之处,当这是一种(正确地)被抽象出来的设计模式时,您正在使用继承来提供这个容器标准模板库。正如 John Zwinck 所建议的,工厂模式将提供一种更具表现力的方式来实现您想要的。

/* 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 的类定义,因为我放在那里的唯一更改是构造函数的单个整数以用作唯一标识符。

【讨论】:

  • 是的,我必须同意。我还不想接触设计模式,但它确实是解决这个问题的正确方法。谢谢你,杰米。
【解决方案2】:

尝试使容器成为不派生实体的单独类。然后,如果您想在创建时自动插入,要么让实体构造函数通过引用获取容器并插入自己,要么让容器也充当工厂,在那里它自己创建实体并维护对它们的引用。像这样:

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

我会在这里指出,这一切似乎有点过度设计。 “ObjectManager”……这个名字本身听起来太笼统了。但也许你正试图以“干净”的方式教授一些面向对象编程的东西,那何乐而不为呢。

【讨论】:

  • 是的,我正在努力保持清洁。他知道模板是什么,并且了解继承。作为一般规则,我尽量不要同时介绍太多概念。
【解决方案3】:

通过简单的访问来使用对象容器,即ObjectManager&lt;T&gt;::objectList

当您将ObjectManager&lt;T&gt;::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 来存储指向已创建对象的指针,以便具有随机可访问性。

还有一件事:当对象被销毁时,将其从列表中删除。

【讨论】:

  • 另外,如果你把objectList设为private/protected,你可以通过友谊控制Ships->Missiles的导航能力。
【解决方案4】:

基本上这是一个典型的继承使用不当的例子。 Missile 和 Ship 根本不应该从 ObjectManager 继承。当对象之间存在“某种”关系时(例如,Nuclear Missile 是一种导弹),您应该保留继承。

在这里,您将继承用于它的“机械”属性(自动注册新实例),但为了达到相同的目的,制作对象也很容易,Missile 说,从一些 ManagedObject 通用类派生并使其添加新的实例到一些管理实例列表的唯一全局对象(或者通过覆盖导弹的默认新建和删除来实现不继承)。或者您可以将管理器作为实例化参数传递给 Missile 类,或者通过管理器的一些构造方法创建 Missile,两者都将其添加到容器中。

所有这些解决方案都有效,并且比您的原始代码(也有效)更令人满意,而且可能还有其他我没有想到的。然而,上述建议都没有让我完全满意。根本问题是 C++ 缺乏适当的元类机制(C++ 是关于编译时间效率,而不是灵活性)。

在考虑了不同的可能性后,我想出了下面的代码。该示例仅适用于 Ship 并且不打扰 Missile 或通过模板使用两个类的通用代码(即使它很容易)。我的回答重点是在给定时间保持系统中存在的所有 Ship 实例的最新列表,而不会干预继承。我选择那个解决方案是因为我相信它更像是 KISS(保持愚蠢和简单),而当您必须维护代码时,复杂的解决方案是一个真正的负担。这个想法也是为了避免重新编码 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;
}

【讨论】:

    【解决方案5】:

    为什么不将ObjectManager&lt;T&gt;::for_each() 设为静态方法?

    【讨论】:

    • 你看到我不是什么好处?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多