假设我有一系列类都可以实现相同的接口,可能用于调度:
class Foo : public IScheduler {
public:
Foo (Descriptor d) : IScheduler (d) {}
/* methods */
};
class Bar : public IScheduler {
public:
Bar (Descriptor d) : IScheduler (d) {}
/* methods */
};
现在假设我有一个Scheduler类,您可以要求为给定的描述符启动一个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。也许每种计划的项目类型都有自己的地图实例的方式?设计模式,还是......?
答案 0 :(得分:5)
函数或类静态成员的地址保证是唯一的(只要<
可以看到),因此您可以使用这样的地址作为密钥。
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[]
来避免双重查找并简化函数体。
答案 1 :(得分:2)
这是一种方式。
向IScheduler
添加纯虚拟方法:
virtual const char *getId() const =0;
然后将每个子类放到它自己的 .h 或 .cpp 文件中,并定义函数:
virtual const char *getId() const { return __FILE__; }
此外,要在编译时确实具有确切类型的模板中使用,在相同文件中定义静态方法,您可以在不使用类实例(AKA static polymorphism)的情况下使用它:
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
,除了对A的更改外,还必须添加getId()
(注意:final
keyword identifier with special meaning是C ++ 11的功能,对于早期版本,请将其删除......)
答案 2 :(得分:2)
如果调度程序是一个单例,这将起作用。
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;
}
如果Scheduler不是单例,您可以使用相同的技术使用中央注册表,但将Scheduler * / Descriptor对映射到unique_ptr。
答案 3 :(得分:1)
static_cast
ItemType*
到void*
并将其存储在地图中。
然后,在findOrCreate
中,只需将void*
和static_cast
恢复为ItemType*
。
static_cast
T * - &gt; void * - &gt; T *保证让你回到原始指针。您已经将typeid(ItemType)
用作密钥的一部分,因此只有在请求完全相同的类型时才能保证查找成功。所以这应该是安全的。
如果您还需要调度程序映射中的IScheduler*
,只需存储两个指针。
答案 4 :(得分:1)
如果您知道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将无法编译。因此,如果您需要一些真正动态的东西,您可以临时添加新的调度程序,这不会起作用。但如果这不是一个要求,那么效果很好。