Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this a mixin and can it be done in c++?

I have my own array class template I would like to optionally add functionality to.

As an example of functionality, take multithreading support: in some cases, I need arrays that put #pragma omp atomic just before any update code (a compiler directive that enforces atomic behaviour, the details aren't important). In other cases, I need arrays that don't do this, as I know they will only be updated safely and I need to avoid the performance hit.

Intuitively it should be possible to define a class called AtomicUpdates that I can inherit from. So to define a double array with atomic updates I would say something like

class AtomicDoubleArray : public MyArray<double>, public AtomicUpdates {};

But I can't see how you'd implement that in practice, and also this would break the principle of inherit interface, not implementation.

Can anyone enlighten me as to what I really want to do here?

like image 865
Sideshow Bob Avatar asked Jan 24 '12 13:01

Sideshow Bob


People also ask

Does C++ support mixin?

You can implement mixins in C++ by using CRTP. A prominent example is the class std::enable_shared_from_this .

When should I use mixins?

There are two main situations where mixins are used: You want to provide a lot of optional features for a class. You want to use one particular feature in a lot of different classes.

What is the difference between a mixin and inheritance?

Mixins are sometimes described as being "included" rather than "inherited". In short, the key difference from an inheritance is that mix-ins does NOT need to have a "is-a" relationship like in inheritance. From the implementation point of view, you can think it as an interface with implementations.


1 Answers

Even if you don't end up using them now mixins and policy template arguments are very useful things to understand. In this case they are very similar. First, an array with a mixin base. I have used c++0x mutex rather than openmp but you should get the idea.

#include <iostream>
#include <vector>
#include <mutex>

template <class value_t, class base_t>
class array_t : private base_t {
    std::vector<value_t> v_;
public:
    array_t(size_t sz = 0) : v_ (sz) { }
    value_t get(size_t i) const
    {
        this->before_get();
        value_t const result = v_[i];
        this->after_get();
        return result;
    }
    void set(size_t i, value_t const& x)
    {
        this->before_set();
        v_[i] = x;
        this->after_set();
    }
};

class no_op_base_t {
protected:
    void before_get() const { }
    void after_get() const { }
    void before_set() const { }
    void after_set() const { }
};

class lock_base_t {
    mutable std::mutex m_;
protected:
    void before_get() const { std::cout << "lock\n"; m_.lock(); }
    void after_get() const { std::cout << "unlock\n"; m_.unlock(); }
    void before_set() const { std::cout << "lock\n"; m_.lock(); }
    void after_set() const { std::cout << "unlock\n"; m_.unlock(); }

};

int main()
{
    array_t<double, no_op_base_t> a (1);
    array_t<double, lock_base_t> b (1);
    std::cout << "setting a\n";
    a.set(0, 1.0);
    std::cout << "setting b\n";
    b.set(0, 1.0);
    std::cout << "getting a\n";
    a.get(0);
    std::cout << "getting b\n";
    b.get(0);
    return 0;
}

Now the same class but using a policy argument approach rather than inheritance.

#include <iostream>
#include <vector>
#include <mutex>

template <class value_t, class policy_t>
class array_t {
    policy_t policy_;
    std::vector<value_t> v_;
public:
    array_t(size_t sz = 0) : v_ (sz) { }
    value_t get(size_t i) const
    {
        policy_.before_get();
        value_t const result = v_[i];
        policy_.after_get();
        return result;
    }
    void set(size_t i, value_t const& x)
    {
        policy_.before_set();
        v_[i] = x;
        policy_.after_set();
    }
};

class no_op_base_t {
public:
    void before_get() const { }
    void after_get() const { }
    void before_set() const { }
    void after_set() const { }
};

class lock_base_t {
    mutable std::mutex m_;
public:
    void before_get() const { std::cout << "lock\n"; m_.lock(); }
    void after_get() const { std::cout << "unlock\n"; m_.unlock(); }
    void before_set() const { std::cout << "lock\n"; m_.lock(); }
    void after_set() const { std::cout << "unlock\n"; m_.unlock(); }

};

int main()
{
    array_t<double, no_op_base_t> a (1);
    array_t<double, lock_base_t> b (1);
    std::cout << "setting a\n";
    a.set(0, 1.0);
    std::cout << "setting b\n";
    b.set(0, 1.0);
    std::cout << "getting a\n";
    a.get(0);
    std::cout << "getting b\n";
    b.get(0);
    return 0;
}

In this case both are very similar. The important difference is that the mixin could define some methods to be virtual and allow you to change the behaviour of array by inheriting from it. As in the following:

template <class value_t>
class mk_virtual_base_t {
protected:
    void before_get() const { }
    void after_get() const { }
    void before_set() const { }
    void after_set() const { }

    virtual value_t get(size_t) const = 0;
    virtual void set(size_t, value_t) = 0;
};

template <class value_t>
class daily_wtf_contender_t : public array_t<value_t, mk_virtual_base_t<value_t> > {
    virtual value_t get(size_t) const { std::cout << "surprise! get is virtual!\n"; return 0; }
    virtual void set(size_t, value_t) { std::cout << "surprise! set is virtual!\n"; }
};

While there are some real cases where the mixin advantage is useful it is not that often. So when working with templates the policy approach is often the more appropriate. Policy arguments are used by the standard library in many places so there are some good examples for you to study.

As for your question about "inherit interface, not implementation." Used carefully inheriting implementation is quite useful. Same goes for multiple inheritance. You just need to be judicious about when you use them.

like image 142
Bowie Owens Avatar answered Sep 28 '22 05:09

Bowie Owens