2012-01-24 2 views
5

У меня есть свой собственный шаблон класса массива. Я хотел бы дополнительно добавить функциональность.Является ли это mixin, и это можно сделать в C++?

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

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

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

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

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

+2

Звучит для меня как добавление параметра политики в шаблон 'MyArray', вероятно, будет работать лучше. –

ответ

5

Даже если вы не используете их, теперь mixins и аргументы шаблона политики - очень полезные вещи для понимания. В этом случае они очень похожи. Во-первых, массив с базой mixin. Я использовал mutex 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; 
} 

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

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 преимуществом является полезным, это не так часто. Поэтому при работе с шаблонами политический подход часто становится более подходящим. Аргументы политики используются стандартной библиотекой во многих местах, поэтому есть несколько хороших примеров для изучения.

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

+1

Есть еще одно важное отличие, а именно, что только первый код позволяет компилятору выполнять EBO - пустую оптимизацию базы. Так как у многих классов политики нет данных, это обычно делает производный класс меньшим, может улучшить выравнивание памяти последующих членов данных. Вот почему большинство (все?) Политик в стандартных реализациях библиотек и в таких библиотеках, как Boost, в частном порядке унаследованы, даже если они не используют виртуальные функции. –

+0

+1 для справки по ежедневному wtf – bronekk

+0

Спасибо, это намного яснее. Но почему политический подход не может определять виртуальные методы? –

Смежные вопросы