Skip to content

Conversion algorithm details

Andreas Bülling edited this page Sep 8, 2016 · 24 revisions

![Alt text](http://g.gravizo.com/g? @startuml; interface BaseInterface; class Base; class Intermediate; class Derived1; class Derived2; BaseInterface <|-- Base; Base <|-- Intermediate; Intermediate <|-- Derived1; Intermediate <|-- Derived2; @enduml; )

Virtual constructor pattern

The base in the algorithm creation design is the virtual constructor pattern. It is a very useful pattern in C++ where you do not know the concrete type of the class you want to create. In our case the user chooses an algorithm name in the GUI list and the converter is expected to create an algorithm object from this name. This is realized using the virtual constructor pattern, let's take a look at the most basic case of this pattern.

struct Base
{
  virtual ~Base(){}
  
  // Interface for children
  virtual void action() = 0;

  // Decides which child class to create
  // from the key used. This is the actual
  // "virtual constructor".
  static Base* Create(const std::string& key);

};

Base* Base::Create(const std::string* key)
{
  if (key == "something")
  {
    return new Derived1();
  }
  return new Derived2();
  
{

struct Derived1
{
  virtual ~Derived1(){}

  // Action implementation for Derived1.
  virtual void action();
}

struct Derived2
{
  virtual ~Derived2(){}

  // Action implementation for Derived2.
  virtual void action();
}

int main()
{
  Base* p1 = Base::Create("something");
  p1->action(); // Action for Derived1.

  Base* p2 = Base::Create("something else");
  p2->action(); // Action for Derived2.
}

However you notice in the example above that for each new class you need to add its creation in the Base::Create function and this is not the case for us. I wanted to design the addition of new algorithm classes in a way such that no existing code would need to be modified. This to make it as easy as possible to add new algorithms.

To achieve this we use a static exemplar of each derived class that is mapped to the corresponding key that should be used in the virtual constructor. Each derived class must not also provide a function for how they are created, this function will be called on the exemplar in the virtual constructor in order to create a new instance of an object of that type. The example above would look something like this with the addition of the static examplar.

struct Base
{
  virtual ~Base(){}

  Base(const std::string& key);
  
  // Interface for children
  virtual void action() = 0;
  
  // Creation function to be implemented
  // by derived classes.
  virtual Base* create() = 0;

  // Decides which child class to create
  // from the key used. This is the actual
  // "virtual constructor".
  static Base* Create(const std::string& key);

private:
  static map<string, Base*> exemplars;

};

Base::Base(const std::string& key)
{
  exemplars[key] = this; // Register exemplar of derived class.
}

Base* Base::Create(const std::string* key)
{
  if (exemplars.find(iKey) != exemplars.end())
  {
    return exemplars[key]->create();
  }
  return 0;
{

struct Derived1
{  
  // Constructor used for registration of exemplar.
  Derived1(const std::string& key) : Base(key) {}
  virtual ~Derived1(){}

  // Creation function for Derived1.
  virtual Base* create();

  // Action implementation for Derived1.
  virtual void action();

  static std::string key;

private:
  static Derived1 _exemplar;
}

Derived1 Derived1::_exemplar("something");

Base* Derived1::create()
{
  return new Derived1();
}

struct Derived2
{
  // Constructor used for registration of exemplar.
  Derived2(const std::string& key) : Base(key) {}
  virtual ~Derived2(){}

  // Creation function for Derived2.
  virtual Base* create();

  // Action implementation for Derived2.
  virtual void action();

  static std::string key;

private:
  static Derived2 _exemplar;
}

Derived2 Derived2::_exemplar("something else");

Base* Derived2::create()
{
  return new Derived2();
}

int main()
{
  Base* p1 = Base::Create("something");
  p1->action(); // Action for Derived1.

  Base* p2 = Base::Create("something else");
  p2->action(); // Action for Derived2.
}

We see now that the Base class is completely generic and will never have to be modified again :). New Derived classes can be added and they just have to register themselves using the exemplar. However we see that there is quite some boilerplate code that is always the same in the derived classes. If you noticed in the algorithm implementation in this project you do not have to add this boilerplate. How to get around this is described in the next section.

Curiously recurring template pattern

Clone this wiki locally