Это миксин и это можно сделать на с ++? - PullRequest
5 голосов
/ 24 января 2012

У меня есть свой собственный шаблон класса массива, к которому я бы хотел добавить функциональность.

В качестве примера функциональности возьмите поддержку многопоточности: в некоторых случаях мне нужны массивы, которые помещают #pragma omp atomic непосредственно перед любымобновить код (директива компилятора, обеспечивающая атомарное поведение, детали не важны).В других случаях мне нужны массивы, которые этого не делают, поскольку я знаю, что они будут обновляться только безопасно, и мне нужно избегать снижения производительности.

Интуитивно понятно, что можно определить класс с именем * 1006.* что я могу унаследовать от.Итак, чтобы определить двойной массив с атомарными обновлениями, я бы сказал что-то вроде

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

Но я не понимаю, как вы реализовали бы это на практике, а также это нарушило бы принцип наследоватьинтерфейс, а не реализация .

Может кто-нибудь просветить меня, что я действительно хочу здесь делать?

1 Ответ

5 голосов
/ 25 января 2012

Даже если вы не используете их сейчас, миксины и аргументы шаблонов политики очень полезны для понимания.В этом случае они очень похожи.Во-первых, массив с базой mixin.Я использовал мьютекс c ++ 0x вместо openmp, но вы должны понять.

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

Теперь тот же класс, но с использованием подхода аргументов политики, а не наследования.

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

В этом случае оба очень похожи.Важным отличием является то, что миксин может определять некоторые методы как виртуальные и позволять вам изменять поведение массива, наследуя его.Как в следующем:

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

Хотя есть некоторые реальные случаи, когда преимущество mixin полезно, это не так часто.Таким образом, при работе с шаблонами подход политики часто является более подходящим.Аргументы политики используются стандартной библиотекой во многих местах, поэтому есть несколько хороших примеров для изучения.

Что касается вашего вопроса о «наследовать интерфейс, а не реализацию».Использовать тщательно наследуемую реализацию довольно полезно.То же самое касается множественного наследования.Вам просто нужно быть осторожным, когда вы их используете.

...