5

假设我有一个类都实现了相同的接口,也许是为了调度:

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 的情况下获得类似的结果。也许每个计划项目类型都可以拥有自己的地图实例?一种设计模式,还是……?

4

5 回答 5

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[]以避免双重查找并简化函数体。

于 2013-10-25T19:56:58.540 回答
2

这是一种方法。

将纯虚方法添加到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()
  • finalA继承Abase,并且必须添加getId()
  • finalA2继承Abase,并且必须添加getId(),除了对 A 的更改

(注:具有特殊含义的final 关键字标识符是 C++11 特性,早期版本略去...)

于 2013-10-25T19:31:58.093 回答
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。

于 2013-10-25T19:53:48.523 回答
1

static_cast并将ItemType*void*存储在地图中。然后,在 中findOrCreate,只需将void*static_cast返回到ItemType*

static_casting T* -> void* -> T* 保证让你回到原来的指针。您已经将typeid(ItemType)其用作密钥的一部分,因此可以保证只有在请求完全相同的类型时查找才会成功。所以应该是安全的。

如果您还需要IScheduler*调度程序映射中的 ,只需存储两个指针。

于 2013-10-25T19:32:20.170 回答
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 将无法编译。因此,如果您需要真正动态的东西,您可以在其中临时添加新的调度程序,这将行不通。但如果这不是一个要求,这很好。

于 2013-10-25T19:54:32.760 回答