模型中虚拟索引实现的替代方法



所以我再次遇到了不能与模板混合的QObject的限制(至少不能直接)。基本上,我有一个代理模型类,它使用索引将源位置转换为本地位置并返回。索引可以通过多种方式实现,现在我需要两个版本,一个使用 QHash,一个使用 QVector .索引的接口对两者都是通用的,在索引操作方面只有细微的区别。使用模板这很容易,我会将类作为模板,然后在这两种情况下使用专业化。

但是,该模型需要是一个QObject,因此似乎我需要像这样使用多态性:

class IndexInterface;
class VectorIndex; //inherits IndexInterface
class HashIndex; //inherits IndexInterface
class ProxyModel : public QObject
{
    Q_OBJECT
public:
    enum IndexType { Vector, Hash };
    explicit ProxyModel(IndexType indexType, QObject *parent = 0) :
        QObject(parent), 
        index(indexType == Vector ? new VectorIndex : new HashIndex) 
    {
    }
    //...
private:
    IndexInterface *index = nullptr;
};

我对此有几个问题。首先,它需要动态分配索引,我想摆脱它。其次,由于使用指向IndexInterace的指针来调度对索引的调用,因此索引的任何方法都不会内联(我已经查看了分散的代码来确认这一点,并尝试了各种优化等无济于事)。

理想情况下,如果没有动态分配索引和对索引的虚拟调用,这种设计的替代方案是什么?

使特定于索引类型的类成为基类之一:

template <typename Index> class IndexHandler {
};
using VectorIndexHandler = IndexHandler<QVector>;
using HashIndexHandler = IndexHandler<QHash>;
class VectorIndexProxy : public QAbstractItemModel, VectorIndexHandler {
  ... // should be very small
};
class HashIndexProxy : public QAbstractItemModel, HashIndexHandler {
  ... // should be very small
};

然后,不要将索引类型传递给构造函数,而是使用工厂函数:

QAbstractItemModel * proxyFactory(IndexType indexType, QObject * parent = 0) {
  switch (indexType) {
  case Foo::Vector:
    return new VectorIndexProxy(parent);
  ...
  }
}

如果你设想一个比QAbstractItemModel更广泛或不同的接口,你当然需要编写这样一个基类,并在具体的实现中派生它。

如果需要,可以使用 CRTP IndexHandler直接调用派生类的方法,使其更小:

template <typename Index, typename Derived> class IndexHandler {
  Derived * derived() { return static_cast<Derived*>(this); }
  const Derived * derived() const; // as above
  void foo() {
    derived()->setObjectName("Yay");
  }
};
class VectorIndexProxy : 
  public QAbstractItemModel, 
  public VectorIndexHandler<QVector, VectorIndexProxy>
{
  ... // should be very small
};

您还可以将基类中的方法"提升"为 Qt 插槽:

class VectorIndexProxy : ... {
#ifdef Q_MOC_RUN
   Q_SLOT void foo();
#endif
};

请参阅有关具有信号和插槽的基本非QObject类的问题。

最后,您可以使用 PIMPL 惯用语,并根据需要获得固定类型的具体实现。工厂将在构造函数中调用,您将在不同的 PIMPL 中交换不同的索引。这并不像你想象的那么昂贵,因为所有Qt类都已经动态分配了一个PIMPL,所以你可以通过从QObjectPrivate#include <private/qobject_p.h>)派生你的PIMPL并将PIMPL的实例传递给受保护的QObject(QObjectPrivate&)来捎带该分配。这种模式在Qt中无处不在,所以即使它是一个实现细节,它至少不会在Qt 5中消失。下面是一个粗略的草图:

// ProxyModel.cpp
#include <private/qobject_p.h>
class ProxyModelPrivate : public QObjectPrivate { 
  // Note: you don't need a q-pointer, QObjectData already provides it
  // for you! CAVEAT: q-pointer is not valid until the QObject-derived-class's
  // constructor has returned. This would be the case even if you passed
  // the q-pointer explicitly, of course.
  ... 
}; // base class
class VectorProxyModelPrivate : public ProxyModelPrivate { ... };
class ProxyModel : public QObject
{
  Q_OBJECT
  Q_DECLARE_PRIVATE(ProxyModel)
  ProxyModel * pimpl(IndexType indexType) {
    switch (indexType) {
    case Vector: return new VectorProxyModelPrivate();
    ...
  }
public:
    enum IndexType { Vector, Hash };
    explicit ProxyModel(IndexType indexType, QObject *parent = 0) :
        QObject(*pimpl(IndexType), parent)
    {}
};

如果你是从QAbstractItemModel派生的,你的PIMPL将以同样的方式从QAbstractItemModelPrivate派生;这适用于Qt中的任何QObject派生类!

最新更新