how to organize a container of views?
-
i'm making a widget container class, that is an arbitrary number of views can be added to it:
class Viewer : public QFrame { Q_OBJECT public: /// Constructor explicit Viewer(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Window) noexcept; void addView(int nIndex, QWidget *pView); QWidget *getView(int nIndex) const; protected: /// This map contains the views of the viewer QHash<int, QWidget *> m_mapViews; /// The widget container layout QSplitter *m_pWidgetSplitter = nullptr; }; // class Viewer Viewer::Viewer(QWidget *parent /* = nullptr */, Qt::WindowFlags flags /* = Qt::Window */) noexcept : QFrame(parent, flags) , m_pWidgetSplitter(new QSplitter(this)) { // configure the layout auto pWidgetsLayout = new QHBoxLayout(this); pWidgetsLayout->setContentsMargins(0, 0, 0, 0); pWidgetsLayout->addWidget(m_pWidgetSplitter); } void Viewer::addView(int nIndex, QWidget *pView) { // save in the map m_mapViews[nIndex] = pView; // and add to the widgets layout m_pWidgetSplitter->insertWidget(nIndex, pView); } QWidget *Viewer::getView(int nIndex) const { return m_mapViews.contains(nIndex) ? m_mapViews[nIndex] : nullptr; }
adding views is easy:
Browser::Browser(QWidget *parent /* = nullptr */, Qt::WindowFlags flags /* = Qt::Window */) : Viewer(parent, flags) , m_pTreeView(new Tree(this)) , m_pTableView(new Table(this)) { addView(Widgets::TreeView, m_pTreeView); addView(Widgets::TableView, m_pTableView); }
the problem here is that i need to save the pointers to
get
them easily:Tree *Browser::getTree() const { return m_pTreeView; } Table *Browser::getTable() const { return m_pTableView; }
what i'd like to do is to use the
getView
function ofViewer
, but in that case i need to deal with casts a lot:Tree *Browser::getTree() const { return static_cast<Tree *>(getView(Widgets::TreeView)); }
how can i do this effectively?
-
You could, for example, give your classes an explicit type id:
class Tree { enum {TypeId = Widgets::TreeView }; }; class Table { enum {TypeId = Widgets::TableView }; };
Then you could make a template wrapper:
template<class T> T* getView() { return static_cast<T*>(getView(T::TypeId)); }
and use it like this:
Tree* tree = getView<Tree>(); Table* table = getView<Table>();
If you don't want to modify your view classes like that you can do something similar outside of them i.e. make a
QHash<QMetaObject*, int> m_metaTypes
member and modifyaddView
to do this:m_metaTypes[pView->metaObject()] = nIndex;
and then the
getView
template wrapper would look something like this:template<class T> T* getView() { int nIndex = m_metaTypes[T::staticMetaObject()]; return static_cast<T*>(getView(nIndex)); }
-
i don't get how it's different or better from my version of
getView
. it's still using casts. what if i get the view a lot? it's casting all the time. -
I thought your worry was about the amount of times you had to write that cast i.e. a separate function for each type. In that case the difference is you only write that single wrapper instead of a function for each type.
Are you actually worried about the performance of that cast? static_cast of one pointer type to another is next to nothing and in many cases, with optimizations turned on, it's actually nothing. Remember that a processor has no notion of those types. All pointers are just numbers. In some cases a processor might need to add an offset to a number when you cast which is just totally insignificant compared to other things you do in that code. A call to
operator[]
in thegetView
method is like dozens times more costly. A function call is way more costly. If you're worried about performance on that level you're definitely looking in the wrong places. -
okay i get it.
so in your first suggestion you call
getView(int)
fromgetView()
which is againoperator[]
.by the way, i wrote
Tree *Browser::getTree() const { return static_cast<Tree *>(getView(Widgets::TreeView)); } Table *Browser::getTable() const { return static_cast<Table *>(getView(Widgets::TableView)); }
to hide the implementation of the getter. although, since i'm working with views, having
getView<type>();
makes sense too. i guess i'll use that option.