inventario - control de biblioteca en c++
¿Cómo integrar una biblioteca que utiliza plantillas de expresión? (4)
Me gustaría usar la biblioteca de matriz Eigen como el motor de álgebra lineal en mi programa. Eigen utiliza plantillas de expresión para implementar la evaluación perezosa y para simplificar los bucles y cálculos.
Por ejemplo:
#include<Eigen/Core>
int main()
{
int size = 40;
// VectorXf is a vector of floats, with dynamic size.
Eigen::VectorXf u(size), v(size), w(size), z(size);
u = 2*v + w + 0.2*z;
}
Como Eigen usa plantillas de expresión, código como
u = 2*v + w + 0.2*z;
En la muestra mencionada anteriormente, la reducción se reduce a un solo bucle de longitud 10 (no 40, los flotadores se colocan en regiser por trozos de 4) sin crear un temporal. ¿Cuan genial es eso?
Pero si integro la biblioteca así:
class UsingEigen
{
public:
UsingEigen(const Eigen::VectorXf& data):
data_(data)
{}
UsingEigen operator + (const UsingEigen& adee)const
{
return UsingEigen(data_ + adee.data_);
}
...
private:
Eigen::VectorXf data_;
}
Luego las expresiones como:
UsingEigen a, b, c, d;
a = b + c + d;
No se puede aprovechar la forma en que se implementa Eigen. Y esto no es lo último. Hay muchos otros ejemplos, donde las plantillas de expresión se utilizan en Eigen.
La solución fácil sería no definir los operadores por mí mismo, hacer públicos los datos y simplemente escribir expresiones como:
UsingEigen a, b, c, d;
a.data_ = b.data_ + c.data_ + d.data_;
Esto rompe la encapsulación, pero preserva la eficiencia de Eigen.
Otra forma podría ser hacer mis propios operadores, pero dejar que devuelvan plantillas de expresión. Pero como soy un principiante en C ++, no sé si este es el camino correcto.
Lo siento si la pregunta es de naturaleza demasiado general. Soy un principiante y no tengo a quien preguntar. Hasta ahora estaba usando std::vector<float>
todas partes, pero ahora también necesito usar matrices. Pasar de std::vector<float>
a Eigen en todo mi proyecto es un gran paso y me da miedo hacer una llamada errónea desde el principio. Cualquier consejo es bienvenido!
¿Por qué exponer data_
break encapsulation? La encapsulación significa ocultar los detalles de la implementación y solo exponer la interfaz. Si su clase contenedora UsingEigen
no agrega ningún comportamiento o estado a la biblioteca Eigen
nativa, la interfaz no cambia. En este caso, debe eliminar este contenedor y escribir su programa utilizando las estructuras de datos Eigen
.
Exponer una matriz o un vector no rompe la encapsulación: solo exponer la implementación de la matriz o el vector haría eso. La biblioteca Eigen
expone los operadores aritméticos pero no su implementación.
Con las bibliotecas de plantillas de expresión, la forma más común para que los usuarios amplíen la funcionalidad de la biblioteca es agregando el comportamiento, no agregando el estado. Y para agregar comportamiento, no necesita escribir clases de envoltura: también puede agregar funciones que no son miembros que se implementan en términos de las funciones de miembros de la clase Eigen
. Consulte esta columna "Cómo las funciones de no miembros mejoran la encapsulación" por Scott Meyers.
En cuanto a su preocupación de que la transformación de su programa actual a una versión que usa explícitamente la funcionalidad Eigen
: puede realizar el cambio paso a paso, cambiando pequeñas partes de su programa cada vez, asegurándose de que su unidad de prueba ( pruebas unitarias, ¿no es así?) no se rompen a medida que avanza.
Configure una plantilla de clase para contener expresiones generales de Eigen y haga de UsingEigen
una instancia especial de ella:
template<typename expr_t>
class UsingEigenExpr
{
UsingEigen(expr_t const& expr) : expr(expr) {}
expr_t expr;
operator UsingEigenExpr<Eigen::VectorXf>() const
{
return {expr};
}
};
using UsingEigen = UsingEigenExpr<Eigen::VectorXf>;
Luego sobrecargue cualquier función requerida, por ejemplo, como
template<typename expr1_t, typename expr2_t, typename function_t>
auto binary_op(UsingEigenExpr<expr1_t> const& x, UsingEigenExpr<expr2_t> const& y, function_t function)
{
return UsingEigenExpr<decltype(function(std::declval<expr1_t>(),std::declval<expr2_t>()))>(function(x.expr,y.expr));
}
template<typename expr1_t, typename expr2_t>
auto operator+(UsingEigenExpr<expr1_t> const& x, UsingEigenExpr<expr2_t> const& y)
{
return binary_op(x,y,[](auto const& x, auto const& y) {return x+y;});
}
y así sucesivamente para otros operadores binarios como operator-
, para operadores unarios, y más generalmente para todas las otras cosas que desee utilizar. Además, podría agregar algunas otras funciones miembro a UsingEigenExpr
, por ejemplo, size()
, norm()
, etc.
Usalo como
UsingEigen b, c, d;
auto a = b + c + d;
para almacenar la expresión, o
UsingEigen b, c, d;
UsingEigen a = b + c + d;
para evaluarlo directamente.
Aunque este enfoque funciona, al final se encuentra duplicando toda la funcionalidad requerida, así que utilícelo con cuidado.
En mi opinión, esto se parece más a un problema de diseño orientado a objetos que a un problema de uso de la biblioteca. Lo que lea de los libros son las recomendaciones correctas. es decir, no exponga las variables miembro y proteja las capas superiores de los matices del uso de la capa de terceros.
Lo que podría mirar hacia adelante son las abstracciones correctas de las funciones matemáticas que pueden implementarse usando esta biblioteca internamente. es decir, podría exponer su propia biblioteca con funciones de alto nivel que las operaciones de vectores y matrices elementales. De esta manera, puede utilizar las peculiaridades de las interacciones entre los objetos de la biblioteca y, al mismo tiempo, no tiene que exponer sus variables miembro a las capas superiores.
Por ejemplo, podría abstraer mis API de nivel superior, como calcular la distancia desde un punto a un plano, la distancia entre dos planos, calcular las nuevas coordenadas de un punto y otro sistema de coordenadas utilizando las matrices de transformación, etc. Para implementar estos métodos internamente, puede utilizar los objetos de la biblioteca. Puede restringir para no tener ninguna de las clases de biblioteca utilizadas en las firmas de la API para evitar la dependencia de las capas superiores de esta biblioteca.
Las capas superiores de su programa deben ser más altas en el nivel de abstracción y no deben preocuparse por los detalles de implementación elementales, como la forma en que se implementa el cálculo de la distancia desde un punto hasta el plano, etc. Además, ni siquiera necesitan saber si esto es menor. La capa se implementa utilizando esta biblioteca o algo más. Simplemente usarían las interfaces de tu biblioteca.
No entiendo todas tus preguntas, intentaré responderte la mayoría de ellas. En esta oración:
UsingEigen operator + (const UsingEigen& adee)const
{
return UsingEigen(data_ + adee.data_);
}
Tiene un operador de sobrecarga (lo siento, no sé si esta es la forma correcta de escribir en inglés), por esta razón puede escribir:
a = b + c + d;
en lugar de:
a.data_ = b.data_ + c.data_ + d.data_;
No tendrás ningún problema, el costo de tu programa será el mismo. Además tendrás encapsulación y eficiencia.
Por otro lado, si desea definir su propio operador, puede hacerlo como lo hace la plantilla. Puede encontrar información en la web buscando "operador de sobrecarga" pero es similar a esto:
UsingEigen operator + (const UsingEigen& adee)const
{
return UsingEigen(data_ + adee.data_);
}
En lugar de "+" puede poner el operador y realizar las operaciones que necesita.
Si quieres crear una matriz es simple. Solo necesitas crear una matriz de matriz o vector de vector.
Creo que es algo como esto:
std::vector<vector<float>>
No estoy seguro, pero es fácil, por otro lado, puedes usar una matriz simple de esta manera:
flotar YourMatrix [tamaño] [tamaño];
Espero que te pueda ayudar. No entiendo todas sus preguntas si necesita algo más, agrégueme en google + y trataré de ayudarlo.
Lo siento por mi inglés, espero que puedas entenderlo todo y que te ayude.