What are Mixins (as a concept)
Asked Answered
P

7

104

I'm trying to get my head around the Mixin concept but I can't seem to understand what it is. The way I see it is that it's a way to expand the capabilities of a class by using inheritance. I've read that people refer to them as "abstract subclasses". Can anyone explain why?

I'd appreciate if you'd explain your answer based on the following example (From one of my lecture slideshows): A C++ Mixin Example

Picket answered 12/9, 2013 at 20:3 Comment(2)
A framework that makes heavy use of mixins is Apache Tapestry for Java web applications. Read the documentation and look at some examples in Tapestry and maybe you will be able to see the parallels/patterns to what you are seeing in the C++ example. Here is a link: tapestry.apache.org/component-mixins.htmlKingwood
I totally thought you were talking about Ruby just by a glance of the title...Bloodline
A
176

Before going into what a mix-in is, it's useful to describe the problems it's trying to solve. Say you have a bunch of ideas or concepts you are trying to model. They may be related in some way but they are orthogonal for the most part -- meaning they can stand by themselves independently of each other. Now you might model this through inheritance and have each of those concepts derive from some common interface class. Then you provide concrete methods in the derived class that implements that interface.

The problem with this approach is that this design does not offer any clear intuitive way to take each of those concrete classes and combine them together.

The idea with mix-ins is to provide a bunch of primitive classes, where each of them models a basic orthogonal concept, and be able to stick them together to compose more complex classes with just the functionality you want -- sort of like legos. The primitive classes themselves are meant to be used as building blocks. This is extensible since later on you can add other primitive classes to the collection without affecting the existing ones.

Getting back to C++, a technique for doing this is using templates and inheritance. The basic idea here is you connect these building blocks together by providing them via the template parameter. You then chain them together, eg. via typedef, to form a new type containing the functionality you want.

Taking your example, let say we want to add a redo functionality on top. Here's how it might look like:

#include <iostream>
using namespace std;

struct Number
{
  typedef int value_type;
  int n;
  void set(int v) { n = v; }
  int get() const { return n; }
};

template <typename BASE, typename T = typename BASE::value_type>
struct Undoable : public BASE
{
  typedef T value_type;
  T before;
  void set(T v) { before = BASE::get(); BASE::set(v); }
  void undo() { BASE::set(before); }
};

template <typename BASE, typename T = typename BASE::value_type>
struct Redoable : public BASE
{
  typedef T value_type;
  T after;
  void set(T v) { after = v; BASE::set(v); }
  void redo() { BASE::set(after); }
};

typedef Redoable< Undoable<Number> > ReUndoableNumber;

int main()
{
  ReUndoableNumber mynum;
  mynum.set(42); mynum.set(84);
  cout << mynum.get() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // back to 84
}

You'll notice I made a few changes from your original:

  • The virtual functions really aren't necessary here because we know exactly what our composed class type is at compile-time.
  • I've added a default value_type for the second template param to make its usage less cumbersome. This way you don't have to keep typing <foobar, int> everytime you stick a piece together.
  • Instead of creating a new class that inherits from the pieces, a simple typedef is used.

Note that this is meant to be a simple example to illustrate the mix-in idea. So it doesn't take into account corner cases and funny usages. For example, performing an undo without ever setting a number probably won't behave as you might expect.

As a sidenote, you might also find this article helpful.

Authenticity answered 12/9, 2013 at 22:21 Comment(7)
This example is actually very good, I actually read it and was surprised to find it made lots of sense lolol. Well done mate. Thanks.Jacinto
A note for readers, void Number::set(int) and int Number::get() const should both be virtual to get the mixin behavior when using a Number* pointer.Eleven
Keeping base class set and get virtual makes sense because you can then define void doubler(Number &n){ n.set(n.get()*2); } and be able to use is with undoable and reundoable classesRawlins
A suggestion: when Number::value_type is already defined it could (and should) also be used for Number::n, Number::get and Number::set.Culicid
Based on my understanding of the reasoning in this post, it seems the same line of reasoning can be used to say that the native/primitive types of any programming language (e.g. int, std::string, char, etc in C++) are themselves mixins, right?Kopp
@Kopp well yes. strictly speaking according to this definition any object is a mixin. because you can make a tuple of those objects and you will have 1 object that has the properties of many. however a mixins are a more general concept.Transfinite
You had me until "templates and inheritance" lol.Ulita
E
12

A mixin is a class dessigned to provide functionality for another class, normally through a specified class which provides the basic features that the functionality needs. For example, consider your example:
The mixin in this case provides the functionality of undoing the set operation of a value class. This hability is based on the get/set functionality provided by a parametrized class (The Number class, in your example).

Another example (Extracted from "Mixin-based programming in C++"):

template <class Graph>
class Counting: public Graph {
  int nodes_visited, edges_visited;
public:
  Counting() : nodes_visited(0), edges_visited(0), Graph() { }
  node succ_node (node v) {
    nodes_visited++;
    return Graph::succ_node(v);
  }
  edge succ_edge (edge e) {
    edges_visited++;
    return Graph::succ_edge(e);
  }
... 
};

In this example, the mixin provides the functionality of counting vertices, given a graph class that performs trasversal operations.

Commonly, in C++ mixins are implemented through the CRTP idiom. This thread could be a good read about a mixin implementation in C++: What is C++ Mixin-Style?

Here is an example of a mixin that takes advantage of the CRTP idiom (Thanks to @Simple):

#include <cassert>
#ifndef NDEBUG
#include <typeinfo>
#endif

class shape
{
public:
    shape* clone() const
    {
        shape* const p = do_clone();
        assert(p && "do_clone must not return a null pointer");
        assert(
            typeid(*p) == typeid(*this)
            && "do_clone must return a pointer to an object of the same type"
        );
        return p;
    }

private:
    virtual shape* do_clone() const = 0;
};

template<class D>
class cloneable_shape : public shape
{
private:
    virtual shape* do_clone() const
    {
        return new D(static_cast<D&>(*this));
    }
};

class triangle : public cloneable_shape<triangle>
{
};

class square : public cloneable_shape<square>
{
};

This mixin provides the functionality of heterogeneous copy to a set (hierarchy) of shape classes.

Estell answered 12/9, 2013 at 20:31 Comment(11)
The example is not CRTP at all.Analysand
@PetrBudnik oops, thats true :)Estell
But I liked your comment referring to CRTP. Because mixins are approximated with CRTP in C++.Analysand
@PetrBudnik yes, thats why I posted the comment. But as you have noticed, the OP example don't use CRTP at all. Thats why I removed the comment later.Estell
@Estell - can you elaborate on why people call it "inheritance without sub-type polymorphism"? I'm still not entirely sure I understand how this is different than an abstract class which adds functionality to all it's derived classes.Picket
@Shookie In CRTP the base class is a template that looks like base<derived>, so you can't have a container of base* objects. Also, an abstract class uses virtual function which entail runtime overhead; with CRTP you use static_cast to call member functions on the derived class that are resolved statically at compile-time.Putrescent
Anyone got a nice real life example that's easy to comprehend, it would finish this answer and set of comments off nicely I think.Jacinto
@JamesT ok, an exampleEstell
@Putrescent thanks, I have added that to the answer. Your example provides the hability of "be cloneable" to a class, right? Heterogeneous copy is the correct term? I'm not sure.Estell
@Estell It allows you to define classes with an is-a relationship with the shape class, but deriving from cloneable_shape automatically implements the clone member function for you, so you don't have to write it yourself for every class you derive.Putrescent
@Putrescent thats what I have understood, but I didn't entirely sure about the term. Thanks anywayEstell
K
12

I like the answer from greatwolf, but would offer one point of caution.

greatwolf stated, "The virtual functions really aren't necessary here because we know exactly what our composed class type is at compile-time." Unfortunately, you can run into some inconsistent behavior if you use your object polymorphically.

Let me tweak the main function from his example:

int main()
{
  ReUndoableNumber mynum;
  Undoable<Number>* myUndoableNumPtr = &mynum;

  mynum.set(42);                // Uses ReUndoableNumber::set
  myUndoableNumPtr->set(84);    // Uses Undoable<Number>::set (ReUndoableNumber::after not set!)
  cout << mynum.get() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // OOPS! Still 42!
}  

By making the "set" function virtual, the proper override will be called and the inconsistent behavior above will not occur.

Kine answered 10/2, 2014 at 23:57 Comment(0)
J
6

Until C++20 CRTP was the standard workaround to realize mixins in C++. A mixin serves the avoidance of code duplication. It is a kind of compile-time polymorphism.

A typical example are iterator support interfaces. Many of the functions are implemented absolutely identically. For example, C::const_iterator C::cbegin() const always calls C::const_iterator C::begin() const.

Note: In C++ struct is the same as class, except members and inheritance are public by default.

struct C {
    using const_iterator = /* C specific type */;

    const_iterator begin() const {
        return /* C specific implementation */;
    }

    const_iterator cbegin() const {
        return begin(); // same code in every iterable class
    }
};

C++ does not yet provide direct support for such default implementations. However, when cbegin() is moved to a base class B, it has no type information about the derived class C.

struct B {
    // ???: No information about C!
    ??? cbegin() const {
        return ???.begin();
    }
};

struct C: B {
    using const_iterator = /* C specific type */;

    const_iterator begin() const {
        return /* C specific implementation */;
    }
};

Starting from C++23: explicit this

The compiler knows the concrete data type of the object at compile time, until C++20 there was simply no way to get this information. Since C++23 you can use explicit this (P0847) to get it.

struct B {
    // 1. Compiler can deduce return type from implementation
    // 2. Compiler can deduce derived objects type by explicit this
    decltype(auto) cbegin(this auto const& self) {
        return self.begin();
    }
};

struct C: B {
    using const_iterator = /* C specific type */;

    const_iterator begin() const {
        return /* C specific implementation */;
    }
};

This type of mixin is easy to implement, easy to understand, easy to use, and robust against typos! It is superior to classic CRTP in every respect.

Historical workaround until C++20: CRTP

With CRTP, you passed the data type of the derived class as a tempalte argument to the base class. Thus, basically the same implementation was possible, but the syntax is much more difficult to understand.

// This was CRTP used until C++20!
template <typename T>
struct B {
    // Compiler can deduce return type from implementation
    decltype(auto) cbegin() const {
        // We trust that T is the actual class of the current object
        return static_cast<T const&>(*this).begin();
    }
};

struct C: B<C> {
    using const_iterator = /* C specific type */;

    const_iterator begin() const {
        return /* C specific implementation */;
    }
};

CRTP was complicated and error prone

Moreover, a really nasty typo could quickly happen here. I'll modify the example a bit to make the consequences of the mistake more obvious.

#include <iostream>

struct C;
struct D;

template <typename T>
struct B {
    decltype(auto) cget() const {
        return static_cast<T const&>(*this).get();
    }
};

struct C: B<C> {
    short port = 80;

    short get() const {
        return port;
    }
};

// Copy & Paste BUG: should be `struct D: B<**D**>`
struct D: B<C> {
    float pi = 3.14159265359f;

    float get() const {
        return pi;
    }
};

int main () {
    D d;

    // compiles fine, but calles C::get which interprets D::pi as short
    std::cout << "Value: " << d.cget() << '\n';
    // prints 'Value: 4059' on my computer
}

That is a very dangerous error, because the compiler cannot detect it!

Jurel answered 9/2, 2023 at 12:45 Comment(2)
CRTP has nothing to do within mixins. Mixins or static decorators can be fully realized without CRTP. There may be situations in a mixin application where you want the base class to know about the derived class at compile time, but this is a totally separate issue and not conceptually related to a mixin. In your example, you need CRTP because of the specific iterator application. That's all...Arand
Also, with respect to the copy-paste error and CRTP being error-prone, the elegant solution for that is to make an abstract base class' constructor private, and to declare the template parameter as a friend of the base class. That way if a user makes a mistake in passing the derived class type as an argument, they won't be able to instantiate, and the compiler WILL detect it.Arand
B
3

Mixins in C++ are expressed using the Curiously Recurring Template Pattern (CRTP). This post is an excellent breakdown of what they provide over other reuse techniques... compile-time polymorphism.

Boule answered 6/3, 2015 at 2:34 Comment(0)
S
0

To understand the concept forget classes for a moment. Think (most popular) JavaScript. Where objects are dynamic arrays of methods and properties. Callable by their name as a symbol or as a string literal. How would you implement that in standard C++ in a year 2018? Not easily. But that is the core of the concept. In JavaScript one can add and remove (aka mix-in) whenever and whatever one wishes to. Very important: No class inheritance.

Now onto C++. Standard C++ has all you need, does not help as a statement here. Obviously I will not write a scripting language in order to implement mix-in using C++.

Yes, this is a good article , but for inspiration only. CRTP is not a panacea. And also the so called academic approach is here, also (in essence) CRTP based.

Before down-voting this answer perhaps consider my p.o.c. code on wand box :)

Spline answered 28/6, 2018 at 8:50 Comment(0)
J
-1

This works the same as an interface and maybe more so as an abstract, but interfaces are easier to get first time.

It addresses many issues but one I find in development that comes up a lot is external apis. imagine this.

You have a database of users, that database has a certain way of getting access to its data. now imagine you have facebook, that also has a certain way of getting access to its data (api).

at any point your application may need to run using data from facebook or your database. so what you do is create an interface that says "anything that implements me is going to definitely have the following methods" now you can implement that interface into your application...

because an interface promises that the implementing repositories will have the methods declared in them, you know that wherever or whenever you use that interface in your application, if you switch the data over, it's always going to have the methods you are defining and thus have data to work off of.

There are many more layers to this pattern of working, but the essence is that it is good because data or other such persistant items become a big part of your application, and if they change without you knowing, your application can break :)

Here's some pseudo code.

interface IUserRepository
{
    User GetUser();
}

class DatabaseUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for database
    }
}

class FacebookUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for facebook
    }
}

class MyApplication
{
    private User user;

    MyApplication( IUserRepository repo )
    {
        user = repo;
    }
}

// your application can now trust that user declared in private scope to your application, will have access to a GetUser method, because if it isn't the interface will flag an error.
Jacinto answered 12/9, 2013 at 20:31 Comment(4)
Your code looks like java, and not like c++. Also, how are mixins different from normal abstract classes?Ezarra
ah well, I do C#, PHP and C++... I actually said they are very similar to abstract, but i find it's harder to explain their use... considering I have yet to use them lots and lots :). Whenever I try come up with a good inheritance example, I make myself cry. The code was to demonstrate easier with wordy syntax, but it is not real code, pseudo I said :) I hope someone answers it much clearer but I personally do like more wordy answers, I find them more helpful and a good real life example too ^_^Jacinto
Ok, it made me wonder. After reading this, I searched the net, and couldn't find a decent explanation. I still do not get it what are mixins :/Ezarra
I understand that you have good intentions, but your explanation makes the picture more blurry than it already was. Not to mention the fact, that you missed the "in C++" part.Proffer

© 2022 - 2025 — McMap. All rights reserved.