假设我有一系列类,它们都实现相同的接口,也许是为了调度:
class Foo : public IScheduler {
public:
Foo (Descriptor d) : IScheduler (d) {}
/* methods */
};
class Bar : public IScheduler {
public:
Bar (Descriptor d) : IScheduler (d) {}
/* methods */
};
现在假设我有一个调度程序类,您可以要求为给定的描述符启动一个 IScheduler 派生类。 如果它已存在,您将获得对它的引用。 如果不存在,则创建一个新的。
一个假设的调用是这样的:
Foo & foo = scheduler->findOrCreate<Foo>(descriptor);
实现这一点需要一个映射,其键(描述符,RTTI)映射到基类指针。 然后你必须dynamic_cast
. 我猜是这样的
template<class ItemType>
ItemType & Scheduler::findOrCreate(Descriptor d)
{
auto it = _map.find(SchedulerKey (d, typeid(ItemType)));
if (it == _map.end()) {
ItemType * newItem = new ItemType (d);
_map[SchedulerKey (d, typeid(ItemType))] = newItem;
return *newItem;
}
ItemType * existingItem = dynamic_cast<ItemType>(it->second);
assert(existingItem != nullptr);
return *existingItem;
}
想知道是否有人有办法在不依赖 RTTI 的情况下获得类似的结果。 也许每种计划项目类型都可以拥有自己的地图实例? 设计模式,还是... ?
或类静态成员的地址保证是唯一的(据<
所知),因此您可以使用这样的地址作为键。
template <typename T>
struct Id { static void Addressed(); };
template <typename ItemType>
ItemType const& Scheduler::Get(Descriptor d) {
using Identifier = std::pair<Descriptor, void(*)()>;
Identifier const key = std::make_pair(d, &Id<ItemType>::Addressed);
IScheduler*& s = _map[key];
if (s == nullptr) { s = new ItemType{d}; }
return static_cast<ItemType&>(*s);
}
请注意使用 operator[]
以避免双重查找并简化函数体。
这是一种方法。
添加一个纯虚拟方法IScheduler
:
virtual const char *getId() const =0;
然后将每个子类放入它自己的 .h 或 .cpp 文件中,并定义函数:
virtual const char *getId() const { return __FILE__; }
此外,对于在编译时具有确切类型的模板,在同一文件中定义静态方法,您可以在没有类实例的情况下使用(AKA 静态多态性):
static const char *staticId() { return __FILE__; }
然后将其用作缓存映射键。 __FILE__
符合C++标准,因此这也是便携式的。
重要说明:使用正确的字符串比较,而不仅仅是比较指针。也许返回std::string
而不是char*
以避免事故。从好的方面来说,您可以与任何字符串值进行比较,将它们保存到文件等,您不必仅使用这些方法返回的值。
如果你想比较指针(比如为了提高效率),你需要更多的代码来确保每个类只有一个指针值(在 .h 中添加私有静态成员变量声明,在相应的.cpp中使用 FILE 进行定义+初始化,然后返回它),并且只使用这些方法返回的值。
注意类层次结构,如果你有类似的东西
-
A
继承IScheduler
必须覆盖getId()
-
A2
继承A
,编译器不会抱怨忘记getId()
然后,如果您想确保不会意外忘记覆盖getId()
,您应该拥有
- 抽象
Abase
继承IScheduler
,而不定义getId()
- 最终
A
继承Abase
,并且必须添加getId()
- 最终
A2
继承Abase
,并且必须添加getId()
,此外还更改了 A
(注意:final
具有特殊含义的关键字标识符是C++11功能,对于早期版本,只需将其省略...
如果调度程序是单例,这将起作用。
template<typename T>
T& Scheduler::findOrCreate(Descriptor d) {
static map<Descriptor, unique_ptr<T>> m;
auto& p = m[d];
if (!p) p = make_unique<T>(d);
return *p;
}
如果调度程序不是单例,则可以使用相同的技术拥有一个中央注册表,但将调度程序*/描述符对映射到unique_ptr。
如果您知道所有不同的IsScheduler
亚型,那么绝对是的。看看Boost.Fusion,它让你创建一个地图,其键实际上是一个类型。因此,对于您的示例,我们可以执行以下操作:
typedef boost::fusion::map<
boost::fusion::pair<Foo, std::map<Descriptor, Foo*>>,
boost::fusion::pair<Bar, std::map<Descriptor, Bar*>>,
....
> FullMap;
FullMap map_;
我们将这样使用该地图:
template <class ItemType>
ItemType& Scheduler::findOrCreate(Descriptor d)
{
// first, we get the map based on ItemType
std::map<Descriptor, ItemType*>& itemMap = boost::fusion::at_key<ItemType>(map_);
// then, we look it up in there as normal
ItemType*& item = itemMap[d];
if (!item) item = new ItemType(d);
return item;
}
如果尝试查找或创建未在FullMap
中定义的项,则at_key将无法编译。因此,如果您需要一些真正动态的东西,您可以在其中临时添加新的调度程序,这是行不通的。但如果这不是必需的,这很好用。
static_cast
ItemType*
void*
并将其存储在地图中。然后,在findOrCreate
中,只需获取void*
并将其static_cast
回ItemType*
即可。
static_cast
T* ->void* -> T* 可以保证让您恢复原始指针。您已使用 typeid(ItemType)
作为密钥的一部分,因此可以保证只有在请求完全相同的类型时查找才会成功。所以这应该是安全的。
如果您还需要调度程序映射中的IScheduler*
,只需存储两个指针即可。