1

So I have again encountered the limits of QObjects that cannot be mixed with templates (at least not directly). Basically I have a proxy model class that uses indexing to translate the source positions to local positions and back. The index can be implemented in number of ways, for now I need two versions, one using QHash and one using QVector. The interface of the index is common to both with only subtle differences regarding index manipulation. With templates this would be easy, I would make the class a template and then used specialization for these two cases.

However the model needs to be a QObject so instead it seems I would need to use polymorphism like so:

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

I have couple of issues with this. First, it requires dynamic allocation of the index which I would like to get rid off. Second, because of the use of pointer to IndexInterace to dispatch the calls to the index no method of the index will ever be inlined (I have looked over dissasembled code to confirm this and tried various optimizations etc. to no avail).

What would be the alternatives to this design ideally without dynamic allocation of the index and without virtual calls to the index?

Resurrection
  • 3,462
  • 2
  • 27
  • 47

1 Answers1

2

Make the index-type-specific class one of the base classes:

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

Then instead of passing the index type to the constructor, use a factory function:

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

If you envision an interface broader or different than QAbstractItemModel, you'll need to write such a base class and derive from it in the concrete implementations, of course.

You could use CRTP if needed for IndexHandler to call directly into the derived class's methods, making it even smaller:

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

You can also "promote" methods from the base class to be Qt slots:

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

See this question about having a base non-QObject class with signals and slots.

Finally, you could use the PIMPL idiom, and have a concrete implementation of a fixed type just like you desire. The factory would be invoked in the constructor and you'd be swapping in different PIMPLs for different indices. That's not as expensive as you think since all Qt classes already dynamically allocate a PIMPL, so you can piggy-back on that allocation by deriving your PIMPL from QObjectPrivate (#include <private/qobject_p.h>), and passing the instance of the PIMPL to the protected QObject(QObjectPrivate&). This pattern is omnipresent in Qt, so even though it's an implementation detail, it's not going away in Qt 5 at the very least. Here's a rough sketch:

// 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)
    {}
};

If you were deriving from QAbstractItemModel, your PIMPL would derive from QAbstractItemModelPrivate, in the same fashion; this works for any QObject-deriving class in Qt!

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 88,505
  • 13
  • 129
  • 275
  • Make sure to include the `Q_OBJECT` macro in the `VectorIndexProxy` and `HashIndexProxy` class definitions in the first example. – anonymous Apr 28 '16 at 00:15
  • @JonHarper That's what's implied in the `...` :) Otherwise it's a slippery slope and the next thing someone else will tell me is that the entire, correct implementation of a model belongs there :) – Kuba hasn't forgotten Monica Apr 28 '16 at 12:19