2010-05-11 10 views
6

Tengo una base de código grande, originalmente C portado a C++ hace muchos años, que está funcionando en una serie de grandes conjuntos de datos espaciales. Estas matrices contienen estructuras que representan entidades de punto y triángulo que representan modelos de superficie. Necesito refactorizar el código de modo que la forma específica en que se almacenan estas entidades internamente varíe para escenarios específicos. Por ejemplo, si los puntos se encuentran en una cuadrícula plana regular, no es necesario que guarde las coordenadas X e Y, ya que se pueden calcular sobre la marcha, al igual que los triángulos. Del mismo modo, quiero aprovechar las herramientas básicas como STXXL para el almacenamiento. La forma más sencilla de hacerlo es reemplazar el acceso a la matriz con funciones de tipo poner y recibir, p. Ej.¿Puedo usar el operador [] en C++ para crear matrices virtuales

point[i].x = XV; 

convierte

Point p = GetPoint(i); 
p.x = XV; 
PutPoint(i,p); 

Como se puede imaginar, este es un refactor muy tedioso en una gran base de código, propenso a todo tipo de errores en el camino. Lo que me gustaría hacer es escribir una clase que imite la matriz al sobrecargar al operador []. Como las matrices ya viven en el montón, y se mueven alrededor con reallocs, el código ya asume que las referencias en la matriz tales como

point *p = point + i; 

no pueden ser utilizados. ¿Es esta clase factible de escribir? Por ejemplo, escribir los métodos a continuación en términos del operador [];

void MyClass::PutPoint(int Index, Point p) 
{ 
    if (m_StorageStrategy == RegularGrid) 
    { 
     int xoffs,yoffs; 
     ComputeGridFromIndex(Index,xoffs,yoffs); 
     StoreGridPoint(xoffs,yoffs,p.z); 
    } else 
     m_PointArray[Index] = p; 
    } 
} 

Point MyClass::GetPoint(int Index) 
{ 
    if (m_StorageStrategy == RegularGrid) 
    { 
     int xoffs,yoffs; 
     ComputeGridFromIndex(Index,xoffs,yoffs); 
     return GetGridPoint(xoffs,yoffs); // GetGridPoint returns Point 
    } else 
     return m_PointArray[Index]; 
    } 
} 

Mi preocupación es que todas las clases de matriz que he visto tienden a pasar por referencia, mientras que yo creo que tenga que pasar estructuras por valor. Creo que debería funcionar algo más que el rendimiento, ¿alguien puede ver las trampas más importantes con este enfoque. nótese bien. La razón por la que tengo que pasar por valor es obtener

point[a].z = point[b].z + point[c].z 

para que funcione correctamente donde varía el tipo de almacenamiento subyacente.

Respuesta

0

Después de leer las respuestas anteriores, decidí que la respuesta de Pete con dos versiones de operator[] era el mejor camino a seguir. Para manejar la transformación entre tipos en tiempo de ejecución, creé una nueva clase de plantilla de matriz que tomó cuatro parámetros de la siguiente manera;

template<class TYPE, class ARG_TYPE,class BASE_TYPE, class BASE_ARG_TYPE> 
class CMorphArray 
{ 
int GetSize() { return m_BaseData.GetSize(); } 
BOOL IsEmpty() { return m_BaseData.IsEmpty(); } 

// Accessing elements 
const TYPE& GetAt(int nIndex) const; 
TYPE& GetAt(int nIndex); 
void SetAt(int nIndex, ARG_TYPE newElement); 
const TYPE& ElementAt(int nIndex) const; 
TYPE& ElementAt(int nIndex); 

// Potentially growing the array 
int Add(ARG_TYPE newElement); 

// overloaded operator helpers 
const TYPE& operator[](int nIndex) const; 
TYPE& operator[](int nIndex); 

    CBigArray<BASE_TYPE, BASE_ARG_TYPE> m_BaseData; 
private: 
    CBigArray<TYPE, ARG_TYPE> m_RefCache; 
    CBigArray<int, int&> m_RefIndex; 
    CBigArray<int, int&> m_CacheIndex; 

    virtual void Convert(BASE_TYPE,ARG_TYPE) = 0; 
    virtual void Convert(TYPE,BASE_ARG_TYPE) = 0; 

    void InitCache(); 
    TYPE& GetCachedElement(int nIndex); 
}; 

El almacenamiento de datos principal está en m_BaseData que es los datos en su formato nativo, que pueden variar en el tipo como se ha discutido. m_RefCache es una matriz secundaria a la caché de elementos en el formato esperado, y la función GetCachedElement utiliza las funciones virtuales Convert para traducir los datos a medida que se mueven dentro y fuera de la caché. El caché debe ser al menos tan grande como el número de referencias simultáneas que pueden estar activas al mismo tiempo, pero en mi caso probablemente se beneficiará al ser más grande, ya que reduce el número de conversiones requeridas. Si bien la implementación del cursor de Alsk probablemente habría funcionado bien, la solución ofrecida requiere menos copias de objetos y variables temporales, y debería ofrecer un rendimiento ligeramente mejor, lo cual es importante en este caso.

Disculpas a todos los fanáticos de STL por el aspecto y sensación antiguos de MFC; el resto del proyecto es MFC, así que tiene más sentido en este caso. El CBigArray fue el resultado de un related stack overflow question que se convirtió en la base de mi gran manejo de matrices. Espero terminar la implementación hoy y probar mañana. Si todo se arruina, editaré esta publicación de forma acosada.

5

No debería necesitar pasar la matriz por valor. Para mutar los valores en la matriz, desea dos versiones de operator[], una que devuelva una referencia (mutar) y otra una referencia constante.

No hay ninguna razón en principio para no usar operator[], siempre y cuando no necesite variar el tipo de almacenamiento en tiempo de ejecución; no hay operadores virtuales, por lo que necesitaría una función con nombre si desea tiempo de ejecución polimorfismo. En ese caso, puede crear un simple struct que adapte las llamadas del operador a las llamadas de función (aunque depende de la API de almacenamiento; si el código asume que la asignación a las variables miembro del punto cambia los datos almacenados, es posible que tenga que hacer el señalar también una variable de plantilla, por lo que puede anularse).

En cuanto a su código de muestra, tiene una prueba para la estrategia de almacenamiento. No hagas esto. Use OO y haga que su objeto de almacenamiento implemente una interfaz virtual común, o (probablemente sea mejor) use la programación de plantillas para variar el mecanismo de almacenamiento.

Si nos fijamos en las garantías hechas por std::vector (en estándares más recientes de C++), entonces es posible tener algo que tenga almacenamiento dinámico y permita el uso de aritmética de punteros, aunque eso requiere un almacenamiento contiguo. Dado que algunos de sus valores se crean sobre la marcha, probablemente no valga la pena colocar esa restricción en sus implementaciones, pero la restricción en sí misma no impide el uso de operator[].

+0

Devolver una referencia al punto implica tener múltiples puntos únicos para todas las referencias potencialmente activas que están activas al mismo tiempo. Esto requerirá un caché de datos transformados, pero debería ser viable. Gracias por la respuesta. –

+0

uso de WRT de una estrategia de almacenamiento en lugar de OO o plantillas, no funcionaría ya que el código existente no está escrito para soportarlo, y un requisito principal es minimizar la cantidad de reescritura. Quiero incluir algo que es polimórfico en tiempo de ejecución en la parte posterior, pero en la parte delantera imita una matriz en C. Si lo escribo desde cero, las plantillas son indudablemente el camino a seguir. –

2

Lo que quiere es posible, pero como también necesita acceso de escritura, el resultado será un poco más complejo a veces.Lo que desea es que la función del colocador no devuelva un "Acceso de escritura puntual" directo, sino una copia temporal, que hará la escritura una vez que la copia salga del alcance.

siguiente fragmento de código intenta esbozar la solución:

class PointVector 
{ 
    MyClass container_; 

    public: 
    class PointExSet: public Point 
    { 
    MyClass &container_; 
    int index_; 

    public: 
    PointExSet(MyClass &container, int index) 
     :Point(container.GetVector(index)),container_(container),index_(index) 
    { 
    } 

    ~PointExSet() 
    { 
     container_.PutVector(index_) = *this; 
    } 
    }; 

    PointExSet operator [] (int i) 
    { 
    return PointExSet(container_,i); 
    } 
}; 

No es tan agradable como usted probablemente esperaría que fuera, pero me temo que no se puede conseguir una mejor solución en C++.

+0

Sí, de ahí la necesidad de referencias. Creo que debería poder hacerlo usando un caché del tipo transformado, con un índice del mismo tamaño que el arreglo utilizado para mantener el caché. –

1

Para tener un control total sobre las operaciones en la matriz, el operador [] debe devolver un objeto especial (inventado hace mucho tiempo y llamado "cursor") que manejará las operaciones por usted. A modo de ejemplo:

class Container 
{ 
    PointCursor operator [] (int i) 
    { 
    return PointCursor(this,i); 
    } 
}; 
class PointCursor 
{ 
public: 
    PointCursor(_container, _i) 
     : container(_container), i(_i), 
     //initialize subcursor 
     x(container, i) {}  

    //subcursor 
    XCursor x; 
private: 
    Container* container; 
    int i; 
}; 
class XCursor 
{ 
public: 
    XCursor(_container, _i) 
     : container(_container), i(_i) {} 

    XCursor& operator = (const XCursor& xc) 
    { 
      container[i].x = xc.container[xc.i].x; 
      //or do whatever you want over x 
    } 

    Container* container; 
    int i; 
} 
//usage 
my_container[i].x = their_container[j].x; //calls XCursor::operator =() 
Cuestiones relacionadas