Building factories in C++ with boost::factory

published at 04.08.2015 15:27 by Jens Weller
Save to Instapaper Pocket

This is the third part of my series on writing applications with C++ using Qt and boost. This time I focus on building factories, using boost::factory, with the ultimate goal to have widget factory producing QWidgets. The last part was about building trees and treemodels for QTreeView.

The video for this part:

boost::factory

boost has a small library focusing on the creation of factories as functional objects, so that boost::factory is part of boost::functional. The library offers templates for creating values and objects (using new), it even can create smart pointers. My actual use case is, to create widgets, which contain the back ends data from a mix of the tree nodes id and type_id. But boost::factory only gives you function objects which create objects, one still has to write a wrapper like class, to have a true factory, where you can register types and create their objects. So I went ahead, and wrote a factory template:

template< class AbstractClass,class IdType = size_t, class MakeType = boost::function< typename std::remove_pointer<AbstractClass>::type*()> >
class Factory
{
    using abstract_type = typename std::remove_pointer<AbstractClass>::type;
    boost::container::flat_map<IdType,MakeType> factory_map;
public:
    void register_factory(IdType type_id,const MakeType& make)
    {
        factory_map.insert(std::make_pair(type_id,make));
    }
    template<class ...args>
    abstract_type* create(IdType id, args&&... a)const
    {
        auto it = factory_map.find(id);
        if(it != factory_map.end())
            return it->second(std::forward<args>(a)...);
        return nullptr;
    }
};

I'm not too eager to mix boost::function/bind and std::function/bind, but boost::factory only really works with boost::function, so that I have to use boost instead of the standard in this case. I want the template to be useable with Type but also Type*, that is why I remove the pointer before I add it again. Yet, this template does not know anything about boost::factory, simply because boost::factory is only needed, when a concrete type needs to be added. The create method simply calls the factory function object, and hands over possible arguments. This is important, as QWidgets usually get a pointer to their parent when created. The constructor arguments are implemented as a variadic template. I opted to use boosts flat_map for storing the connection between typeid and creation object.

Currently missing is the option to remove a type from the factory, this does not apply to my needs currently. The factory is once setup, and then used through out the program. This could change, when things like plugins are added, with the need to add and remove types at run time.

The actual concrete factory class is WidgetFactory, which defines the interface with which the Widgets for the tabcontrol are created:

class WidgetFactory
{
    Factory<QWidget*,size_t,boost::function<QWidget*(QWidget*)> > factory;
    using id_and_hash = std::pair<int,size_t>;
    boost::container::flat_map<id_and_hash,QWidget*> widget_map;
public:
template<class args...> QWidget *createWidget(int id, size_t type_id,args... a); QWidget* removeWidget(int id,size_t type_id); void registerType(size_t type_id, boost::function<QWidget*(QWidget*)> make); };

This class keeps a cache of already created widgets, and exposes the factory functions. I choose not to derive from Factory, but rather add it as an implementation detail to the class. The createWidget method either returns the widget from a cache, or creates the object, adds it to the cache and returns it:

QWidget *createWidget(int id, size_t type_id, QWidget* parent)
{
    auto idnhash = std::make_pair(id,type_id);
    auto it = widget_map.find(idnhash);
    if(it != widget_map.end() )
        return it->second;
    QWidget* widget= factory.create(type_id,parent);
    if(widget)
        widget_map.insert(std::make_pair(idnhash,widget));
    return widget;
}

When an item is to be deleted from the tree, also its widget needs to be removed, hence the need to offer a removal method. The key for the flat_map is a type that joins the instance specific id with the type specific typeid, currently I opted to use std::pair for this. The removeWidget method actually needs to return the QWidget:

QWidget* WidgetFactory::removeWidget(int id,size_t type_id)
{
    id_and_hash idnhash = std::make_pair(id,type_id);
    auto it = widget_map.find(idnhash);
    if(it != widget_map.end())
    {
        QWidget* pwidget = it->second;
        widget_map.erase(it);
        return pwidget;
    }
    return nullptr;
}

In the context where this method is called, the widget is not known, it might needs to removed from the tabcontrol, and of course to be destroyed. This happens in Qt with the call to deleteLater(), as possible active events should be processed first. So this code simply searches for the entry in the map, removes it a returns then the pointer to widget. It returns nullptr for the case that there is no type registered or no widget has been created for this instance. The registerType method simply forwards to the Factory class.

The factory creates widgets, when a node in the Treeview is double clicked. Via Qt Signal/Slot mechanism one can register a slot in the mainwindow to a signal from the treeview. This is the corresponding handler method:

void MainWindow::showItemWidget(QModelIndex index)
{
    if(!index.isValid())return;

    auto item = static_cast<ItemTreeModel::ItemPtr>(index.internalPointer());
    QWidget* w = factory.createWidget(item->id(),item->type_id(),this);
    if(!w)return;
    type2data[item->type_id()](w,item->shared_from_this());
    int tabindex = ui->tabWidget->indexOf(w);
    if(tabindex == -1)
    {
        ui->tabWidget->addTab(w,QString::fromStdString(item->name()));
        tabindex = ui->tabWidget->count()-1;
    }
    ui->tabWidget->setCurrentIndex(tabindex);
}

This is not very complicated code, it needs to obtain the treenode pointer from QModelIndex, and then create the widget. Which is added to the tabcontrol. There is one remain of a previous refactoring still in this code, type2data is used to execute typespecific code after the factory has created the code. Kind of doing a two step initialization. This was because I had a little bit of trouble to get the factory running with constructor arguments. This code can now be refactored, so that the item->shared_from_this() also becomes a constructor argument. In order to use boost::factory with constructor arguments, it needs to be combined with boost::bind:

factory.registerType(dir_typeid,boost::bind(boost::factory<DirPanel*>(),_1));
factory.registerType(page_typeid,boost::bind(boost::factory<PagePanel*>(),_1));

Leaves one more thing, how to create the actual values for the tree inside the TreeView? This is currently done in a template method in the mainwindow class:

template< class T >
void createInstance(QModelIndex& index, const QString& label)
{
    QString txt = QInputDialog::getText(this,"Input Dialog",label);
    if(!txt.isEmpty())
        treemodel->emplace_back(index,T(txt.toStdString()));
}

From the many types which will populate the tree, those who need to be constructed by the user, share the common need to have a name displayed in the tree. So I opted for a template method, which is called with a concrete type: createInstance<Dir>(index, "Enter directory name");. The item then is actually constructed inside the tree directly, plus the tree model doing the update on the tree view.

Part 4: Menus and Widgets in Qt

Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!