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++


A mixin is a fragment of a class in the sense that it is intended to be composed with other classes or mixins. The term "mixin" (or "mixin class") was originally introduced in Flavors (see "Object-Oriented Programming with Flavors," by D.A. Moon, Proceedings of the 1st ACM Conference on Object-Oriented Programming Languages and Applications, 1986), the predecessor of CLOS (Object-Oriented Programming in Common Lisp: A Programmer's Guide to CLOS, by S. Keene, Addison-Wesley, 1989). The difference between a regular, stand-alone class (such as Person) and a mixin is that a mixin models some small functionality slice (for example, printing or displaying) and is not intended for standalone use. Rather, it is supposed to be composed with some other class needing this functionality (Person, for instance). One use of mixins in object-oriented languages involves classes and multiple inheritance. In this model, a mixin is represented as a class, which is then referred to as a "mixin class," and we derive a composed class from a number of mixin classes using multiple inheritance. Another model is to use parameterized inheritance. In this case, we can represent a mixin as a class template derived from its parameter, for example:

template<class Base> 
class Printing : public Base
{...}

Indeed, some programmers define mixins as "abstract subclasses" — that is, subclasses without a concrete superclass (see "Mixin-Based Inheritance," G. Bracha and W. Cook, Proceedings of the 8th Conference on Object-Oriented Programming, Systems, Languages, and Applications/European Conference on Object-Oriented Programming, 1990). Mixins based on parameterized inheritance in C++ have been used to implement highly configurable collaborative and layered designs (for instance, "Using Role Components to Implement Collaboration-Based Designs," by M. VanHilst and D. Notkin, Proceedings of the 1996 ACM Conference on Object-Oriented Programming Systems, Languages and Applications, 1996 and "Implementing Layered Designs with Mixin Layers," Y. Smaragdakis and D. Batory, Proceeding of the 12th European Conference on Object-Oriented Programming, 1998).

In this article, we'll present a solution to the constructor problem with parameterized inheritance-based mixin programming in C++. Listing One illustrates this problem, which was also described in "Mixin-Based Programming in C++," by Y. Smaragdakis and D. Batory (Proceedings of the Second International Symposium on Generative and Component-Based Software Engineering, 2000).

Listing One

#include <iostream>
using namespace std;

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:
    PhoneContact(const char* fn,const char* ln,const char* pn)
      :Base(fn,ln),phone_(pn)
    {}
    void print() const
    {
      Base::print();
      cout << ' ' << phone_;
    }
  private:
    const char *phone_;
};

template <class Base>
class EmailContact: public Base
{
  public:
  EmailContact(const char* fn,const char* ln,const char* e)
      :Base(fn,ln),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;
  // The following composition isn't legal because there
  // is no constructor that takes all four arguments.
  // EmailContact<PhoneContact<Customer> >
  // c4("Eddy","Eagle","049-554433","[email protected]");
  // c4.print(); cout << endl;
  return 0;
}

In Listing One, the mixin classes PhoneContact and EmailContact can easily be composed with Customer; that is, PhoneContact<Customer> and EmailContact<Customer>. In both cases, the constructor interface of the base class (Customer) is known, and the arguments for initializing the base class are passed to the base constructor in the initializer lists of the derived mixin classes (PhoneContact and EmailContact). Although semantically plausible, compositions including both mixins (PhoneContact<EmailContact<Customer> > or EmailContact<PhoneContact<Customer> >, for instance) do not work. This is due to the fact that the necessary constructors accepting four arguments and calling the appropriate base class constructor with three arguments are missing.

There are several partial solutions to the constructor problem. The worst idea is to restrict or change the order in which mixin classes can be composed. Because the previously described example cannot be fixed using this strategy anyway, we will not further explore it. Here, we will describe four partial solutions that are somewhat better, but still suffer from problems such as incurring unnecessary overhead, leading to clumsy client code and poor scalability when adding new mixin classes. Nevertheless, they provide the basic insights for understanding the more advanced, complete solution, which we will also propose.

Partial Solutions

One aspect of the aforementioned problem is that a mixin class with an appropriate constructor simply does not exist. A straightforward solution — at least at first glance — would be to simply implement an extra mixin class with the needed constructor using multiple inheritance; see Listing Two. Unfortunately, this approach requires major changes to the existing code. First, we have to prepare the inheritance hierarchy for using multiple inheritance. To avoid potential duplication of subobjects of the class Customer in PhoneAndEmailContact, Customer has to be changed into a virtual base of PhoneContact and EmailContact. Additionally, PhoneAndEmailContact — as the most-derived class — must take care of initializing the virtual base class.

Listing Two

#include <iostream>
using namespace std;

// Define a new mixin class with a constructor that accepts all arguments.

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

// The new mixin class will be defined using multiple inheritance. 
// Therefore Base must be turned into a virtual base class.
template <class Base>
class PhoneContact: virtual public Base
{
  public:
    PhoneContact(const char* fn,const char* ln,const char* pn)
      :Base(fn,ln),phone_(pn)
    {}
    void print() const
    {
      Base::print();
      basicprint();
    }

  protected:
    // We need an "inner" print method that prints the PhoneContact-specific
    // information only.
    void basicprint() const
    {
      cout << ' ' << phone_;
    }
  private:
    const char *phone_;
};

// Base has to be declared as virtual base class here, too.
template <class Base>
class EmailContact: virtual public Base
{
  public:
    EmailContact(const char* fn,const char* ln,const char* e)
      :Base(fn,ln),email_(e)
    {}

    void print() const
    {
      Base::print();
      basicprint();
    }
  protected:
    // We need an "inner" print method that prints the EmailContact-specific
    // information only.
    void basicprint() const
    {
      cout << ' ' << email_;
    }
  private:
    const char *email_;
};

template <class Base>
class PhoneAndEmailContact :
         public PhoneContact<Base>,
         public EmailContact<Base>
{
  public:
    // Because Base is a virtual base class, PhoneAndEmailContact is now 
    // responsible for its initialization.
    PhoneAndEmailContact(const char* fn,
                         const char* ln,char* pn,const char* e)
     :PhoneContact<Base>(fn,ln,pn),
      EmailContact<Base>(fn,ln,e),
      Base(fn,ln)
      {}
      void print() const
      {
         Base::print();
         PhoneContact<Base>::basicprint();
         EmailContact<Base>::basicprint();
      }
};

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;
  PhoneAndEmailContact<Customer>
  c4("Eddy","Eagle","049-554433","[email protected]");
  c4.print(); cout << endl;
  return 0;
}

An annoying change is that the print() methods of PhoneContact and EmailContact must be split into basicprint() and print(). Otherwise, it would be impossible to produce an appropriate output with PhoneAndEmailContact::print(). (The method splitting technique in the context of virtual base classes is described by Bjarne Stroustrup in The C++ Programming Language, Third Edition, Addison-Wesley, 1997.) Imagine what would happen if we introduced another mixin class — PostalAddress, for example. This would require adding special mixin classes that combine PhoneContact with PostalAddress, EmailContact with PostalAddress, as well as PhoneContact, EmailContact, and PostalAddress. Thus, the number of such combination classes grows exponentially (without considering the composition order).

Providing a Special Argument Class

When you create a special argument class, the basic idea is to provide a standardized interface for the constructors of all mixin classes by introducing a special class that wraps the union of all arguments of all mixin class constructors (see Listing Three). Stroustrup described the technique of bundling arguments in a special argument class in The Design and Evolution of C++ (Addison-Wesley, 1994).

Listing Three

#include <iostream>
using namespace std;
// Define a class that wraps the union of all constructor arguments 
// of Customer and all derived mixin classes. 

// CustomerParameter combines all constructor arguments of CustomerParameter 
// and its derived mixin classes. The default values for the last two 
// arguments provide some convenience to the client programmer.
struct CustomerParameter
{
  const char
    * fn,
    * ln,
    * pn,
    * e;
  CustomerParameter( const char* fn_,const char*ln_,
                     const char* pn_ = "",const char* e_ = "")
    :fn(fn_),ln(ln_),pn(pn_),e(e_)
  {}
};

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

template <class Base>
class PhoneContact: public Base
{
  public:
    PhoneContact(const CustomerParameter& cp)
      :Base(cp),phone_(cp.pn)
    {}
    void print() const
    {
       Base::print();
       cout << ' ' << phone_;
    }
  private:
    const char *phone_;
};

template <class Base>
class EmailContact: public Base
{
  public:
    EmailContact(const CustomerParameter& cp)
      :Base(cp),email_(cp.e)
    {}
    void print() const
    {
      Base::print();
      cout << ' ' << email_;  
    }
  private:
      const char *email_;
};

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

This solution also has several drawbacks. Every time a new mixin class with additional constructor parameters is added, the argument class has to be updated accordingly. Fortunately, the source code of the already defined mixin classes does not break, but only needs to be recompiled. Furthermore, an additional parameter object has to be created when defining the desired object, which is awkward for the client programmer. We should also note that this solution is not very efficient because arguments will be created even if they are not required. By providing default values for optional arguments, we only (partially) hide this fact from the client programmer. This illusion breaks if one of the arguments preceding the last argument is optional. In such a case, the optional argument must be specified, as the declaration of the EmailContact<Customer> object shows.

This solution allows symmetric composition; for instance, PhoneContact<EmailContact<Customer> > and EmailContact<PhoneContact<Customer> >. But it should be recognized that the resulting print() method is composed differently in both cases.


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.