Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Mixin-Based Programming in C++

Source Code Accompanies This Article. Download It Now.


Providing Initialization Methods

When you provide an initialization method (an approach described by Smaragdakis and Batory), you depart from the common C++ practice in that an object has to be properly initialized by executing one of its constructors. Either no constructor is defined — which implies the generation of a default constructor by the compiler — or a standard constructor is implemented that initializes the class members with some reasonable default values. The actual initialization is left to special initialization methods that assign their argument values to the class members (see Listing Four).

Listing Four

#include <iostream>
using namespace std;

// Define special intialization methods in each class and no longer rely 
// on the proper initialization though constructors.

class Customer
{
  public:
    // Initialization method for Customer.
    // A default constructor will be generated automatically.
    void init(const char* fn,const char* ln)
    {
      firstname_ = fn;
      lastname_ = ln;
    }
    void print() const
    {
      cout << firstname_
           << ' '
           << lastname_;
    }
  private:
    const char *firstname_,
               *lastname_;
};

template <class Base>
class PhoneContact: public Base
{
  public:
    // Initialization method for PhoneContact only.
    // A default constructor will be generated automatically.
    void init(const char* pn)
    {
      phone_ = pn;
    }

    void print() const
    {
      Base::print();
      cout << ' ' << phone_;
    }
  private:
    const char *phone_;
};

template <class Base>
class EmailContact: public Base
{
  public:
    // Initialization method for EmailContact only.
    // A default constructor will be generated automatically.
    void init(const char* e)
    {
      email_ = e;
    }

    void print() const
    {
      Base::print();
      cout << ' ' << email_;
    }
  private:
    const char *email_;
};

int main()
{
  // Compiler generated default constructor gets called.
  Customer c1;
  // Now explicitly invoke the initialization method.
  c1.init("Teddy","Bear");
  c1.print(); cout << endl;
  // Basically the same as above.
  PhoneContact<Customer> c2;
  // But initialization method for Customer must also be explicitly invoked!
  c2.Customer::init("Rick","Racoon");
  c2.init("050-998877");
  c2.print(); cout << endl;
  // Basically the same as above.
  EmailContact<Customer> c3;
  c3.Customer::init("Dick","Deer");
  c3.init("[email protected]");
  c3.print(); cout << endl;
  // Now the three initialization methods of three different mixin classes 
  // must be explicitly invoked! The composed class does not provide its own
  // initialization method.
  EmailContact<PhoneContact<Customer> > c4;
  c4.Customer::init("Eddy","Eagle");
  c4.PhoneContact<Customer>::init("[email protected]");
  c4.EmailContact<PhoneContact<Customer> >::init("049-554433");
  c4.print(); cout << endl;
  // Basically the same as above.
  PhoneContact<EmailContact<Customer> > c5;
  c5.Customer::init("Eddy","Eagle");
  c5.EmailContact<Customer>::init("[email protected]");
  c5.PhoneContact<EmailContact<Customer> >::init("049-554433");
  c5.print(); cout << endl;
  return 0;
}

This approach is error-prone because the client programmer is responsible for calling the necessary initialization methods in the correct order. This is important because one cannot generally assume that the initialization of the members of derived classes never depends on the base class members. Furthermore, inherited initialization methods must be invoked using explicit qualification syntax, which is awkward.

Defining Additional Constructors

When you define additional constructors, you make use of the template instantiation mechanism of C++. It is important to know that only those parts of a template class that are actually used get instantiated. Consequently, it is possible that partial instantiations of a given template class will be legal, even when a full instantiation is not. This lets you define the different constructors in mixin classes for the different possible base classes; see Listing Five.

Listing Five

#include <iostream>
using namespace std;

// Define additional constructors that will be instantiated only if required.

class Customer
{
  public:
    Customer(const char* fn,const char* ln):firstname_(fn),lastname_(ln)
    {}
    void print() const
    {
      cout << firstname_
           << ' '
           << lastname_;
    }
  private:
    const char *firstname_,
               *lastname_;
};

template <class Base>
class PhoneContact: public Base
{
  public:
    // The following constructors will be instantiated only if required.
    PhoneContact( const char* fn,const char* ln,
                  const char* pn):Base(fn,ln),phone_(pn)
    {}
    PhoneContact( const char* fn,const char* ln,
                  const char* pn,const char* e)
      :Base(fn,ln,e),phone_(pn)
    {}
    void print() const

    {
      Base::print();
      cout << ' ' << phone_;
    }
  private:
      const char *phone_;
};

template <class Base>
class EmailContact: public Base
{
  public:
    // The following constructors will be instantiated only if required.
    EmailContact( const char* fn, const char* ln,
                  const char* e):Base(fn,ln),email_(e)
    {}
    EmailContact( const char* fn,const char* ln,
                  const char* pn,const char* e)
      :Base(fn,ln,pn),email_(e)
    {}
    void print() const
    {
      Base::print();
      cout << ' ' << email_;
    }

  private:
    const char *email_;
};

int main()
{
  Customer c1("Teddy","Bear");
  c1.print(); cout << endl;
  PhoneContact<Customer> c2("Rick","Racoon","050-998877");
  c2.print(); cout << endl;
  EmailContact<Customer> c3("Dick","Deer","[email protected]");
  c3.print(); cout << endl;
  EmailContact<PhoneContact<Customer> >
  c4("Eddy","Eagle","049-554433","[email protected]");
  c4.print(); cout << endl;
  PhoneContact<EmailContact<Customer> >
  // The following composition prints the last two arguments in reverse 
  // order because the print() method is composed differently than previously.
  c5("Eddy","Eagle","049-554433","[email protected]");
  c5.print(); cout << endl;
  return 0;
}

At first glance, this approach might look attractive. But after introducing a new mixin class with special arguments for its initialization, additional constructors would have to be implemented in all dependent classes. Depending on the number of possible compositions of mixin classes, this could result in a maintenance nightmare. Please note that, as already described, composition of the same mixin classes in a different order may lead to different behaviors; for instance, PhoneContact<EmailContact<Customer> > and EmailContact<PhoneContact<Customer> > lead to different implementations of the print() method. Thus, the number of different compositions may grow more than exponentially.

Designing an Advanced Solution

The previously described approaches of providing a special argument class and defining additional constructors provide the basic framework for designing the more advanced solution.

First, it is a good idea to provide a special class that wraps the constructor arguments and thus provides a uniform constructor interface. What should be achieved then is the provision of different argument wrappers for different compositions of mixin classes. We will see how to solve this problem by applying some template-metaprogramming techniques such as described in our book Generative Programming: Methods, Tools, and Applications (Addison-Wesley, 2000; see http://www.generative-programming.org/).

Second, the client programmer should be able to adhere to the usual way of declaring objects without the obscuring and awkward argument wrapper construction syntax. Thanks to member templates, highly generic constructors can be implemented that take ordinarily specified arguments and automatically convert them to instances of the argument wrapper classes.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.