我有一个有几个对象的应用程序(到目前为止约有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将根据传入的类型创建对象。
C#是否已经破坏了我,C ++无法做到这一点,或者有没有办法实现这一点,我没有看到它?
答案 0 :(得分:9)
是的,有办法。即使在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
答案 1 :(得分:5)
我解决这个问题的方法是使用我称之为静态注册表模式的东西,在我看来,它是依赖注入的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;
}
优点:
缺点:
上述实现非常简单,您可以根据您的要求以多种不同的方式扩展它。
答案 2 :(得分:3)
而不是BrokeredObject基类中的GetInterfaceId(),您可以定义纯虚方法:
virtual BrokeredObject& GetInstance()=0;
在派生类中,你将从该方法返回特定派生类的实例,如果它已经创建,如果没有,你将首先创建它然后返回它。
答案 3 :(得分: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 :(得分:2)
看起来您不需要全局对象来进行管理,那么为什么不将所有内容都移到类中呢?
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();
}
答案 5 :(得分:1)
有一个问题,为什么你使用工厂而不是每个类都使用单例模式?
<小时/> 编辑:好的,所以你不想被锁定成单身人士;没问题。关于C ++的精彩之处在于它为您提供了如此多的灵活性。您可以拥有一个GetSharedInstance()成员函数,该函数返回该类的静态实例,但将构造函数保留为public,以便您仍然可以创建其他实例。
答案 6 :(得分:1)
如果您在编译时始终知道类型,那么直接调用BrokeredObject* p = GetObjectByID(TypeA::get_InterfaceID())
而不是TypeA* p = new TypeA
或TypeA 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>;
在任何情况下,每个新课程都可以在注册表中自行注册,使注册分散而不是集中注册。
答案 7 :(得分:0)
你几乎肯定会使用依赖注入。
答案 8 :(得分:0)
为什么不呢?
template BrokeredObject* GetOrCreateObject() { return new T(); }
答案 9 :(得分:0)
我的用例倾向于变得更复杂 - 我需要能够进行一些对象初始化,并且我需要能够根据配置从不同的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
{
};