2012-01-24 10 views
5

Tengo mi propia plantilla de clase de matriz a la que me gustaría agregar funcionalidad opcionalmente.¿Es esto mixin y se puede hacer en C++?

Como ejemplo de funcionalidad, tome soporte multihilo: en algunos casos, necesito arreglos que pongan #pragma omp atomic justo antes de cualquier código de actualización (una directiva de compilación que impone comportamiento atómico, los detalles no son importantes). En otros casos, necesito arreglos que no lo hagan, ya que sé que solo se actualizarán de forma segura y tendré que evitar el impacto en el rendimiento.

Intuitivamente debería ser posible definir una clase llamada AtomicUpdates que puedo heredar. Así que para definir una matriz doble con actualizaciones atómicas que diría algo así como

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

Pero no puedo ver cómo le gustaría poner en práctica que en la práctica, y esto rompería el principio de la interfaz de hereda, no aplicación.

¿Alguien puede aclararme lo que realmente quiero hacer aquí?

+2

Me parece que agregar un parámetro de política a su plantilla 'MyArray' probablemente funcionaría mejor. –

Respuesta

5

Incluso si no terminas usándolos ahora, los mixins y los argumentos de la plantilla de política son elementos muy útiles para comprender. En este caso, son muy similares. Primero, una matriz con una base mixin. He usado C++ 0x mutex en lugar de openmp, pero debería tener la 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; 
} 

Ahora la misma clase pero utilizando un enfoque de argumento de política en lugar de herencia.

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

En este caso, ambos son muy similares. La diferencia importante es que la mezcla podría definir algunos métodos para ser virtual y le permitirá cambiar el comportamiento de la matriz heredando de ella. Como en el siguiente:

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

Si bien hay algunos casos reales donde la ventaja mixin es útil no es tan a menudo. Por eso, cuando se trabaja con plantillas, el enfoque de política suele ser el más apropiado. Los argumentos de política son utilizados por la biblioteca estándar en muchos lugares, por lo que hay algunos buenos ejemplos para estudiar.

En cuanto a su pregunta sobre "heredar interfaz, no implementación". Usado hereda cuidadosamente la implementación es bastante útil. Lo mismo ocurre con la herencia múltiple. Solo necesita ser juicioso acerca de cuándo los usa.

+1

Hay otra diferencia importante, a saber, que solo el primer código permite al compilador realizar EBO - optimización de base vacía. Dado que muchas clases de políticas no tienen miembros de datos, esto generalmente hace que la clase derivada más pequeña pueda mejorar la alineación de la memoria de los miembros de datos posteriores. Esta es la razón por la cual la mayoría de las políticas (¿todas?) En las implementaciones de bibliotecas estándar y en bibliotecas como Boost se heredan de forma privada aunque no usen funciones virtuales. –

+0

+1 para referencia a wtf diario – bronekk

+0

Gracias, eso es mucho más claro. Pero, ¿por qué el enfoque político tampoco puede definir métodos virtuales? –