【问题标题】:Looking for a better C++ class factory寻找更好的 C++ 类工厂
【发布时间】:2008-12-12 17:12:20
【问题描述】:

我有一个包含多个对象的应用程序(到目前为止大约 50 个,但还在增长)。应用程序中每个对象只有一个实例,并且这些实例在组件之间共享。

我所做的是从基础 BrokeredObject 类派生所有对象:

class BrokeredObject
{
  virtual int GetInterfaceId() = 0;
};

并且每个对象类型都返回一个唯一的 ID。这些 ID 保存在头文件中。

然后我有一个 ObjectBroker“工厂”。当有人需要一个对象时,调用 GetObjectByID()。 boker 在 STL 列表中查找对象是否已经存在,如果存在,则返回它。如果没有,它会创建它,将其放入列表中并返回它。一切顺利。

BrokeredObject *GetObjectByID(int id)
{
  BrokeredObject *pObject;
  ObjectMap::iterator = m_objectList.find(id);
  // etc.
  if(found) return pObject;

  // not found, so create
  switch(id)
  {
    case 0: pObject = new TypeA; break;
    case 1: pObject = new TypeB; break;
    // etc.
    // I loathe this list
  }
  // add it to the list
  return pObject;
}

我觉得痛苦的是维护这个 ID 列表并且必须让每个类都实现它。通过让每种类型都保存有关其自己的 ID 的信息,我至少让我的消费者的生活稍微轻松了一点,如下所示:

class TypeA : public BrokeredObject
{
  static int get_InterfaceID() { return IID_TYPEA; }
  int GetInterfaceID() { return get_InterfaceID(); }
};

所以我可以得到这样的对象:

GetObjectByID(TypeA::get_InterfaceID());

不必真正知道 ID 映射是什么,但我仍然对维护和潜在的错误不感兴趣。好像知道类型,为什么还要知道ID呢?

我渴望在 C# 中是这样的:

BrokeredObject GetOrCreateObject<T>() where T : BrokeredObject
{
  return new T();
}

ObjectBroker 将根据传入的 type 创建对象。

C# 宠坏了我吗?C++ 无法做到这一点已是不争的事实,还是有一种我没有看到的方法来实现这一点?

【问题讨论】:

  • 您能否详细说明 C# 示例中的内容?我认为您还有其他不清楚的要求。
  • 不要使用 STL 列表,而是使用 map 或 hash_map。如果您有很多对象或经常获取它们,则 list::find 可能会慢很多。
  • “应用程序中每个对象只有一个实例”:哎哟...

标签: c++ design-patterns


【解决方案1】:

是的,有办法。即使在 C++ 中,对于 C# 代码所做的事情也非常简单(尽管不检查继承):

template<typename T>
BrokeredObject * GetOrCreateObject() {
  return new T();
}

这将与 C# 代码一样工作。它也是类型安全的:如果您传递的类型不是从 BrokeredObject 继承的(或者不是该类型本身),那么编译器会在 return 语句中抱怨。然而,它总是会返回一个新对象。

单例

正如另一个人所建议的(感谢他),这一切看起来都非常适合单例模式。只需执行TypeA::getInstance() 即可将唯一的实例存储在该类的静态变量中。我想这会比上述方法容易得多,不需要 ID 来解决它(我之前在这个答案中展示了一种使用模板存储 ID 的方法,但我发现它实际上就是单例)。

我了解到,您将有机会拥有多个类的实例。一种方法是拥有一个Mingleton(这个词是我编的:))

enum MingletonKind {
    SINGLETON,
    MULTITON
};

// Singleton
template<typename D, MingletonKind>
struct Mingleton {
    static boost::shared_ptr<D> getOrCreate() {
        static D d;
        return boost::shared_ptr<D>(&d, NoopDel());
    }

    struct NoopDel {
        void operator()(D const*) const { /* do nothing */ }
    };
};

// Multiton
template<typename D>
struct Mingleton<D, MULTITON> {
    static boost::shared_ptr<D> getOrCreate() {
        return boost::shared_ptr<D>(new D);
    }
};

class ImASingle : public Mingleton<ImASingle, SINGLETON> {
public:
    void testCall() { }
    // Indeed, we have to have a private constructor to prevent
    // others to create instances of us.
private:
    ImASingle() { /* ... */ }
    friend class Mingleton<ImASingle, SINGLETON>;
};

class ImAMulti : public Mingleton<ImAMulti, MULTITON> {
public:
    void testCall() { }
    // ...
};

int main() {
    // both do what we expect.
    ImAMulti::getOrCreate()->testCall();
    ImASingle::getOrCreate()->testCall();
}

现在,您只需使用SomeClass::getOrCreate(),它就会关心细节。 shared_ptr 的单例情况下的自定义删除器使删除成为无操作,因为 shared_ptr 拥有的对象是静态分配的。不过要注意静态变量的销毁顺序问题:Static initialization order fiasco

【讨论】:

  • 在这种情况下,单例是一种很好的模式,但是如果您使用的是多线程应用程序,则需要注意单例的多线程问题。
  • 不需要返回指针。返回参考。然后用户不需要验证它不是 NULL。
  • 原本想返回一个引用。但我对此感到难过,因为它是由新创建的。我不会将 refs 与 new 联系起来,这对我来说看起来很奇怪 :) 这样,他们就可以将它按原样填充到 shared_ptr 中。
  • 在 Singleton 的情况下,析构函数也应该是私有的,这样用户就不能删除接收到的指针。在 Multiton 的情况下,析构函数必须对用户代码可用,以便释放资源。
  • dribeas,哦,对了,我忘了将 dtor 设为私有 -.- 好吧,我想他无论如何都会想出一些宏来做到这一点。类似于谷歌的 DISABLE_EVIL_CONSTRUCTORS(ClassName); :p
【解决方案2】:

我解决这个问题的方法是使用我所说的静态注册表模式,在我看来,它是 C++ 版本的依赖注入。

基本上,您有一个类型的构建器对象的静态列表,可用于构建另一种类型的对象。

一个基本的静态注册表实现如下所示:

template <class T>
class StaticRegistry
{
public:
    typedef std::list<T*>   Container;

    static  StaticRegistry<T>&  GetInstance()
    {
        if (Instance == 0)
        {
            Instance = new StaticRegistry<T>;
        }
        return *Instance;
    }

    void    Register(T* item)
    {
        Items.push_back(item);
    }

    void    Deregister(T* item)
    {
        Items.remove(item);
        if (Items.empty())
        {
            delete this;
            Instance = 0;
        }
    }

    typedef typename Container::const_iterator  const_iterator;

    const_iterator begin() const
    {
        return Items.begin();
    }

    const_iterator end() const
    {
        return Items.end();
    }

protected:
    StaticRegistry() {}
    ~StaticRegistry() {}

private:
    Container               Items;

    static StaticRegistry<T>*   Instance;
};

template <class T>
StaticRegistry<T>* StaticRegistry<T>::Instance = 0;

BrokeredObjectBuilder 的实现可能如下所示:

class BrokeredObjectBuilderBase {
public:
    BrokeredObjectBuilderBase() { StaticRegistry<BrokeredObjectBuilderBase>::GetInstance().Register(this); }
    virtual ~BrokeredObjectBuilderBase() { StaticRegistry<BrokeredObjectBuilderBase>::GetInstance().Deregister(this); }

    virtual int GetInterfaceId() = 0;
    virtual BrokeredObject* MakeBrokeredObject() = 0;
};


template<class T>
class BrokeredObjectBuilder : public BrokeredObjectBuilderBase {
public:
    BrokeredObjectBuilder(unsigned long interface_id) : m_InterfaceId(interface_id) { } 
    virtual int GetInterfaceId() { return m_InterfaceId; }
    virtual T* MakeBrokeredObject() { return new T; }
private:
    unsigned long m_InterfaceId;
};


class TypeA : public BrokeredObject
{
   ...
};

// Create a global variable for the builder of TypeA so that it's 
// included in the BrokeredObjectBuilderRegistry
BrokeredObjectBuilder<TypeA> TypeABuilder(TypeAUserInterfaceId);

typedef StaticRegistry<BrokeredObjectBuilderBase> BrokeredObjectBuilderRegistry;

BrokeredObject *GetObjectByID(int id)
{
  BrokeredObject *pObject(0);
  ObjectMap::iterator = m_objectList.find(id);
  // etc.
  if(found) return pObject;

  // not found, so create
  BrokeredObjectBuilderRegistry& registry(BrokeredObjectBuilderRegistry::GetInstance());
  for(BrokeredObjectBuilderRegistry::const_iterator it = registry.begin(), e = registry.end(); it != e; ++it)
  {
    if(it->GetInterfaceId() == id)
    {
      pObject = it->MakeBrokeredObject();
      break;
    }
  }

  if(0 == pObject)
  {
    // userinterface id not found, handle this here
    ...
  }      

  // add it to the list
  return pObject;
}

优点:

  • 知道创建类型的所有代码都被分离到构建器中,并且 BrokeredObject 类不需要知道它。
  • 此实现可在库中使用,您可以在每个项目级别控制使用多种不同技术将哪些构建器拉入项目。
  • 构建器可以是您希望的复杂或简单(如上)。

缺点:

  • 涉及的基础设施很少(但不会太多)。
  • 定义全局变量以包含要包含在项目中的构建器的灵活性确实使使用起来有点混乱。
  • 我发现人们很难理解这种模式,我不知道为什么。
  • 有时很难随时了解静态注册表中的内容。
  • 上述实现泄漏了一位内存。 (我可以忍受...)

上面的实现很简单,你可以根据你的需求用很多不同的方式来扩展它。

【讨论】:

  • wxWidgets 使用这种模式或非常相似的模式来执行内部 wxRTTI。您添加一个新的小部件,您不必更改 wxRTTI 即可了解您的小部件,因为静态继承会为您完成。您可以通过 id 创建控件。
【解决方案3】:

使用模板类作为代理。
使实例成为函数的静态成员。它将在首次使用时创建,并在程序退出时自动销毁。

template <class Type>
class BrokeredObject
{
    public:
        static Type& getInstance()
        {
            static Type theInstance;

            return theInstance;
        }
}; 

class TestObject
{
    public:
       TestObject()
       {}
};


int main()
{
    TestObject& obj =BrokeredObject<TestObject>::getInstance();
}

【讨论】:

  • 在对另一个问题的评论中,作者说:全局对象还有其他一些功能。它实际上处理功能配置以及对象类型是否应该能够存在,具体取决于可用的硬件和其他因素。
【解决方案4】:

您可以定义纯虚方法,而不是 BrokeredObject 基类中的 GetInterfaceId():

virtual BrokeredObject& GetInstance()=0;

在派生类中,您将从该方法返回特定派生类的实例,如果它已经创建,如果没有,您将首先创建它然后返回它。

【讨论】:

  • 您将使用什么对象来调用该方法?没有诸如“虚拟静态”方法之类的东西(这是什么意思)。
  • 每个派生类都是单例。这些是我将用来调用该方法的对象。
【解决方案5】:

看起来您不需要全局对象来进行管理,那么为什么不将所有内容都移到类中呢?

template <class Type>
class BrokeredObject
{
protected:
    static Type *theInstance;

public:
    static Type *getOrCreate()
    {
        if (!theInstance) {
            theInstance = new Type();
        }

        return theInstance;
    }

    static void free()
    {
        delete theInstance;
    }

};

class TestObject : public BrokeredObject<TestObject>
{
public:
    TestObject()
    {}

};


int
main()
{
    TestObject *obj = TestObject::getOrCreate();
}

【讨论】:

  • 全局对象还有其他一些功能。它实际上处理功能配置以及对象类型是否应该能够存在,具体取决于可用的硬件和其他因素。
  • 那么如果 BrokeredObject::getInstance 从某个全局配置对象中获得了必要的信息呢?
  • 您希望人们手动调用 free()?谁叫它。多个对象可以检索和存储指针的副本,因此谁负责调用 free()!
  • 您可以通过多种方式处理释放,例如,将对象注册到一个全局对象,该全局对象将在进程退出时释放它们,因为无论如何他都有一个全局对象。或者,因为它们看起来是单例,所以根本不释放它们。
【解决方案6】:

如果您启用了RTTI,您可以使用typeid 获取类名。

一个问题,为什么你使用工厂而不是为每个类使用单例模式?


编辑:好的,所以你不想被锁定在一个单例中;没问题。 C++ 的美妙之处在于它为您提供了如此多的灵活性。您可以有一个 GetSharedInstance() 成员函数,该函数返回该类的静态实例,但将构造函数保持为公共,以便您仍然可以创建其他实例。

【讨论】:

  • 我在没有 RTTI 的 Windows CE 下运行。我没有使用单例模式,因为在某些情况下对象不一定是单例,我不想在这一点上严格执行它并最终不得不在以后更改它。
【解决方案7】:

如果您在编译时总是知道类型,那么直接调用BrokeredObject* p = GetObjectByID(TypeA::get_InterfaceID()) 而不是TypeA* p = new TypeATypeA o 就没有什么意义了。

另一方面,如果您在编译时不知道确切的类型,则可以使用某种类型的注册表。

template <class T>
BrokeredObject* CreateObject()
{
    return new T();
}

typedef int type_identity;
typedef std::map<type_identity, BrokeredObject* (*)()> registry;
registry r;

class TypeA : public BrokeredObject
{
public:
     static const type_identity identity;
};

class TypeB : public BrokeredObject
{
public:
     static const type_identity identity;
};

r[TypeA::identity] = &CreateObject<TypeA>;
r[TypeB::identity] = &CreateObject<TypeB>;

或者如果您启用了 RTTI,您可以使用 type_info 作为 type_identity:

typedef const type_info* type_identity;
typedef std::map<type_identity, BrokeredObject* (*)()> registry;
registry r;

r[&typeid(TypeA)] = &CreateObject<TypeA>;
r[&typeid(TypeB)] = &CreateObject<TypeB>;

当然,在任何情况下,每个新类都可以在注册表中自行注册,从而使注册分散化而不是集中化。

【讨论】:

  • 您需要使用代理的逻辑来完成此操作,这是另一篇帖子评论中的附加要求(代理具有检测是否应该创建对象的逻辑)。无论如何,这是我用于抽象工厂的方法,封装在一个 simgleton 对象中。
【解决方案8】:

几乎可以肯定你应该使用依赖注入。

【讨论】:

  • 我相当肯定你的权利。愿意为我指出一些可以帮助我到达那里的东西吗?
  • 抱歉,我不是 C++ 大用户,只是泛泛而谈。当我研究它时,看起来 C++ 在 DI 需要的许多概念上都遇到了困难,但这是一个尝试过的项目:code.google.com/p/autumnframework
【解决方案9】:

为什么不这样?

模板 BrokeredObject* GetOrCreateObject() { 返回新的 T(); }

【讨论】:

    【解决方案10】:

    我的用例会变得更复杂一些——我需要能够进行一些对象初始化,并且我需要能够根据配置从不同的 DLL 加载对象(例如硬件的模拟与实际) .它开始看起来像是 COM 和 ATL 是我前进的方向,但我不想将 COM 的重量添加到操作系统中(这是在 CE 中完成的)。

    我最终采用的是基于模板的(感谢 litb 让我走上正轨),看起来像这样:

    class INewTransModule
    {
      public:
        virtual bool Init() { return true; }
        virtual bool Shutdown() { return true; }
    };
    
    template <typename T>
    struct BrokeredObject
    {
    public:
        inline static T* GetInstance()
      {
        static T t;
        return &t;
      }
    };
    
    template <> 
    struct BrokeredObject<INewTransModule>
    {
    public:
        inline static INewTransModule* GetInstance()
      {
        static INewTransModule t;
        // do stuff after creation
        ASSERT(t.Init());
        return &t;
      }
    };
    
    class OBJECTBROKER_API ObjectBroker
    {
      public: 
        // these calls do configuration-based creations
        static ITraceTool  *GetTraceTool();
        static IEeprom     *GetEeprom();
        // etc
    };
    

    然后为了确保对象(因为它们是模板化的)实际上被编译,我添加了如下定义:

    class EepromImpl: public BrokeredObject<EepromImpl>, public CEeprom
    {
    };
    
    class SimEepromImpl: public BrokeredObject<SimEepromImpl>, public CSimEeprom
    {
    };
    

    【讨论】:

      猜你喜欢
      • 2013-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-01
      • 2014-07-20
      • 1970-01-01
      • 1970-01-01
      • 2011-05-01
      相关资源
      最近更新 更多