【问题标题】:Alternatives to virtual index implementation in a model模型中虚拟索引实现的替代方案
【发布时间】:2016-04-27 20:02:42
【问题描述】:

所以我又遇到了QObjects 不能与模板混合的限制(至少不能直接混合)。基本上我有一个代理模型类,它使用索引将源位置转换为本地位置并返回。索引可以通过多种方式实现,现在我需要两个版本,一个使用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 的指针来调度对索引的调用,因此不会内联索引的任何方法(我查看了反汇编代码以确认这一点并尝试了各种优化等无济于事)。

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

【问题讨论】:

    标签: c++ qt templates qobject


    【解决方案1】:

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

    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 更广泛或不同的接口,您当然需要编写这样一个基类并在具体实现中从它派生。

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

    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
    };
    

    请参阅this question,了解如何使用带有信号和槽的非 QObject 基类。

    最后,您可以使用PIMPL idiom,并根据您的需要拥有一个固定类型的具体实现。工厂将在构造函数中调用,您将在不同的 PIMPL 中交换不同的索引。这并不像您想象的那么昂贵,因为所有 Qt 类都已经动态分配了 PIMPL,因此您可以通过从 QObjectPrivate (#include &lt;private/qobject_p.h&gt;) 派生 PIMPL 并传递PIMPL 的实例到受保护的QObject(QObjectPrivate&amp;)。这种模式在 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-deriving 类!

    【讨论】:

    • 确保在第一个示例中的VectorIndexProxyHashIndexProxy 类定义中包含Q_OBJECT 宏。
    • @JonHarper 这就是... 中所暗示的内容:) 否则这是一个滑坡,接下来别人会告诉我的是,模型的完整、正确的实现属于那里:)跨度>
    猜你喜欢
    • 2015-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    • 2014-10-22
    • 2013-03-26
    • 2019-10-19
    • 1970-01-01
    相关资源
    最近更新 更多