2010-09-20 44 views
11

Estoy portando código que usa una gran variedad de flotadores, que pueden desencadenar fallas de malloc de c a C++. Hice una pregunta acerca de si debo utilizar vectores o deques y Niki Yoshiuchi generosamente me ofreció este ejemplo de un tipo envuelto con seguridad:¿Cómo puedo definir un operador de dobles corchetes/doble iterador, similar al vector de vectores?

template<typename T> 
class VectorDeque 
{ 
private: 
    enum TYPE { NONE, DEQUE, VECTOR }; 
    std::deque<T> m_d; 
    std::vector<T> m_v; 
    TYPE m_type; 
    ... 
public: 
    void resize(size_t n) 
    { 
    switch(m_type) 
    { 
     case NONE: 
     try 
     { 
     m_v.resize(n); 
     m_type = VECTOR; 
     } 
     catch(std::bad_alloc &ba) 
     { 
     m_d.resize(n); 
     m_type = DEQUE; 
     } 
     break; 
    } 
    } 
}; 

que necesitaba un vector 2D de vectores/deque de deques, por lo que he modificado a la siguiente código:

template<typename T> 
class VectorDeque 
{ 
private: 
    enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR }; 
    std::deque<std::deque<T> > x_d,y_d,z_d; 
    std::vector<std::vector<T> > x_v,y_v,z_v; 
    TYPE my_container; 
public: 
    void resize(size_t num_atoms, size_t num_frames) 
    { 
    switch(m_type) 
    { 
     case NONE: 
     try 
     { 
     x_v.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    x_v[counter].resize(num_frames); 
     y_v.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    y_v[counter].resize(num_frames); 
     z_v.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    z_v[counter].resize(num_frames); 
     my_container = VECTOR; 
     } 
     catch(std::bad_alloc &e) 
     { 
     x_d.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    x_d[counter].resize(num_frames); 
     y_d.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    y_d[counter].resize(num_frames); 
     z_d.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    z_d[counter].resize(num_frames); 
     my_container = DEQUE; 
     } 
     break; 
    } 
    } 
}; 

ahora quiero ser capaz de definir mis operadores del soporte para que pueda tener una declaración como x[1][2] acceder directamente a lo que es el contenedor de memoria verdadera estoy usando (dado por el valor de mi variable enumerada.

He visto un par de tutoriales flotando alrededor de anular el operador de corchetes, pero no tengo ninguna idea de anular los corchetes dobles.

¿Cómo se pueden sobrecargar los soportes dobles?

Además, ¿cómo se sobrecargarían los iteradores dobles (en caso de que quiera usar un iterador, en contraposición a la indexación directa)?

EDIT 1:

Basado en la solución de Martin York/Matteo Italia I ideó la clase siguiente:

template<typename T> 
class VectorDeque2D 
{ 
public: 

    class VectorDeque2D_Inner_Set 
    { 
    VectorDeque2D& parent; 
    int first_index; 
    public: 
    // Just init the temp object 
    VectorDeque2D_Inner_Set(My2D& p, int first_Index) : 
     parent(p), 
     first_Index(first_index) {} 
    // Here we get the value. 
    T& operator[](int second_index) const 
    { return parent.get(first_index,second_index);} 
    }; 

    // Return an object that defines its own operator[] that will access the data. 
    // The temp object is very trivial and just allows access to the data via 
    // operator[] 
    VectorDeque2D_Inner_Set operator[](unsigned int first_index) { 
    return (*this, x); 
    } 


    void resize_first_index(unsigned int first_index) { 
    try { 
     my_vector.resize(first_index); 
     my_container = VECTOR; 
    } 
    catch(std::bad_alloc &e) { 
     my_deque.resize(first_index); 
     my_container = DEQUE; 
    } 
    } 

    void resize_second_index(unsigned int second_index) { 
    try { 
     for (unsigned int couter=0;couter < my_vector.size(); counter++) { 
    my_vector[counter].resize(second_index); 
     } 
     my_container = VECTOR; 
    } 
    catch(std::bad_alloc &e) { 
     for (unsigned int couter=0;couter < my_deque.size(); counter++) { 
    my_deque[counter].resize(second_index); 
     } 
     my_container = DEQUE; 
    } 
    } 
    void resize(unsigned int first_index, 
      unsigned int second_index) { 
    try { 
     my_vector.resize(first_index); 
     for (unsigned int couter=0;couter < my_vector.size(); counter++) { 
    my_vector[counter].resize(second_index); 
     } 
     my_container = VECTOR; 
    } 
    catch(std::bad_alloc &e) { 
     my_deque.resize(first_index); 
     for (unsigned int couter=0;couter < my_deque.size(); counter++) { 
    my_deque[counter].resize(second_index); 
     } 
     my_container = DEQUE; 
    }  
    } 
private: 
    enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR }; 

    friend class VectorDeque2D_Inner_Set; 

    std::vector<std::vector<T> > my_vector; 
    std::deque<std::deque<T> > my_deque; 
    STORAGE_CONTAINER my_container; 

    T& get(int x,int y) { 
    T temp_val; 
    if(my_container == VECTOR) { 
     temp_val = my_vector[first_index][second_index]; 
    } 
    else if(my_container == DEQUE) { 
     temp_val = my_deque[first_index][second_index]; 
    } 

    return temp_val; 
    } 

}; 

Finalmente un recipiente 2D tamaño-safe !! ¡Gracias chicos!

Respuesta

20

Hay dos técnicas principales:

1) el uso del operador() en lugar de operador [].
Esto es porque el operador() permite múltiples parámetros.

class My2D 
{ 
    public: 
     int& operator()(int x,int y) { return pget(x,y);} 
    private: 
     int& pget(int x,int y) { /* retrieve data from 2D storage */ } 
}; 

2) Utilice el operador [] pero devuelva un objeto intermedio.
A continuación, puede aplicar el segundo operador [] al objeto intermedio.

class My2D 
{ 
    public: 
     class My2DRow 
     { 
      My2D& parent; 
      int x; 
      public: 
       My2DRow(My2D& p, int theX) : parent(p), x(theX) {}  // Just init the temp object 
       int& operator[](int y) const { return parent.pget(x,y);} // Here we get the value. 
     }; 

     // Return an object that defines its own operator[] that will access the data. 
     // The temp object is very trivial and just allows access to the data via operator[] 
     My2DRow operator[](int x)  { return My2DRow(*this, x);} 
    private: 
     friend class My2DRow; 
     int& pget(int x,int y) { /* retrieve data from 2D storage */ } 
}; 

int main() 
{ 
    My2D data; 
    int& val = data[1][2]; // works fine. 

    // This is the same as 
    My2D::My2DRow row = data[1]; 
    int&   val2 = row[2]; 
} 

Prefiero la segunda técnica.
Esto se debe a que deja el código original intacto y es más natural de leer (en un contexto de matriz). Por supuesto, usted paga por la simplicidad en el alto nivel con un código un poco más complejo implementando su matriz 2D.

+2

¡Agradable! Una vez más, lo que tú y Matteo ofrecieron es lo que yo consideraría una solución "real". Las personas que se quejaban de que no había forma de hacerlo, simplemente se obsesionaban por el hecho de que no había ningún operador singular [] [] sobrecargado. –

4
¿Cómo se pueden sobrecargar los soportes dobles?

No entendí completamente su pregunta, pero debe sobrecargar los paréntesis y hacer que devuelvan un objeto que sobrecargue su propio operador de corchetes. Por ejemplo, si tiene un vector de vectores, el trabajo ya está hecho: vector < vector < something > > sobrecarga operator[], que arroja un vector< something >; esto, a su vez, se ha sobrecargado el operador paréntesis (y que devuelve un objeto something), por lo que se puede hacer simplemente:

vector<vector<something> > vec; 
// ... 
something s = vec[2][3]; 


Ejemplo con un objeto proxy:

template <typename T> 
class Container 
{ 
private: 
    // ... 


public: 

    // Proxy object used to provide the second brackets 
    template <typename T> 
    class OperatorBracketHelper 
    { 
     Container<T> & parent; 
     size_t firstIndex; 
    public: 
     OperatorBracketHelper(Container<T> & Parent, size_t FirstIndex) : parent(Parent), firstIndex(FirstIndex) {} 

     // This is the method called for the "second brackets" 
     T & operator[](size_t SecondIndex) 
     { 
      // Call the parent GetElement method which will actually retrieve the element 
      return parent.GetElement(firstIndex, SecondIndex); 
     } 

    } 

    // This is the method called for the "first brackets" 
    OperatorBracketHelper<T> operator[](size_t FirstIndex) 
    { 
     // Return a proxy object that "knows" to which container it has to ask the element 
     // and which is the first index (specified in this call) 
     return OperatorBracketHelper<T>(*this, FirstIndex); 
    } 

    T & GetElement(size_t FirstIndex, size_t SecondIndex) 
    { 
     // Here the actual element retrieval is done 
     // ... 
    } 
} 

(add sobrecargada métodos const cuando sea apropiado :))

Tenga en cuenta que con este método no pierde casi nada con respecto a una implementación operator(), ya que la recuperación todavía se realiza en un solo lugar, sin co nstraints sobre el uso de los dos índices, teniendo ambos índices en el momento de realizar la recuperación, y sin devolver objetos temporales "gordos" (OperatorBracketHelper es tan grande como dos punteros, y el compilador puede optimizarlo fácilmente).

+0

Quiero ser capaz de hacer referencia a los datos contenidos en mi tipo VectorDeque utilizando doble corchetes. Su ejemplo funcionaría de forma diferente a lo que trato de hacer ya que no estoy tratando de definir [] para VectorDeque, estoy tratando de definir [] [] para VectorDeque, porque mi declaración es simplemente VectorDeque(); no VectorDeque

+2

Bueno, era solo un ejemplo. Tenga en cuenta que * no existe un operador [] [], solo hay un operador [] que puede devolver un tipo que sobrecarga nuevamente al operador []. En este caso, debe crear un proxy-objeto temporal para devolver en el operador de VectorDeque [], que mantendría una referencia a su objeto principal y le pedirá el resultado "correcto" cuando se llame a su operador []. Trataré de agregar un ejemplo en un minuto. –

+0

Ah, entonces hay una manera, simplemente no a través de sobrecarga directa ... –

1

No sobrecargar el operador [], sobrecargar el operador ().

ver este enlace: Overloading Subscript Operator.

le recomiendo la lectura a través de la C++ FAQ Lite al menos una vez antes de la publicación de desbordamiento de pila. Además, la búsqueda de Desbordamiento de pila también puede arrojar información útil.

+0

+1 para usar() en su lugar.A menudo se pasa por alto cuando uno implementa la indexación multidimensional. (Dios sabe que lo he hecho más de unas cuantas veces ... :) – Macke

+0

Desnatado sobre esa sección y su solución. Menciona que puedes usar [] []. Leí su razonamiento de por qué normalmente no querrías, pero quiero preservar el carácter vector-ish/deque-ish, así que creo que en este caso vale la pena las desventajas. ¿Pero cómo lo harías? Él no ofrece muchos detalles. Estoy pensando que necesito clases anidadas o algo así, pero ¿cómo puedo preservar el manejo de try/catch en una estructura anidada? –

+2

@Jason R. Mick: Al igual que con muchas cosas en las Preguntas frecuentes sobre C++, es muy parcial y crítico y no estoy de acuerdo. Proporcionar la capacidad de usar [] [] para que el código sea legible es la forma en que recomendaría hacerlo, ya que hace que el código sea más fácil de leer y mantener. (A costa de hacer que su clase de matriz sea un poco más difícil de leer). A continuación, proporciono una plantilla simple de cómo hacerlo sin exponer detalles de implementación y, por lo tanto, proporcionar flexibilidad (PS. Este es un patrón relativamente estándar (no algo que inventé)). –

2

No hay operadores de "doble paréntesis" en C++. Lo que necesita hacer es definir un único operador [] y hacer que devuelva una referencia a otro objeto, que a su vez puede responder a su propio operador []. Esto se puede anidar tantos niveles profundos como requiera.

Por ejemplo, cuando crea un vector de vectores, el operador [] en el vector externo devuelve una referencia a uno de los vectores internos; el operador [] en ese vector devuelve una referencia a un elemento individual del vector.

std::vector<std::vector<float> > example; 
std::vector<float> & first = example[0]; // the first level returns a reference to a vector 
float & second = example[0][0]; // the same as first[0] 
+0

Vea el ejemplo de Matteo a continuación para una solución ... –

+1

@Jason, no creo que haya entendido mi respuesta. No dije que no pudieras hacerlo; Comencé diciendo que la respuesta más obvia era la forma incorrecta de pensar sobre la pregunta, y luego detallé la forma correcta de obtener lo que deseaba. Pensé que mi explicación era bastante simple y directa, por favor díganme dónde fallé. –

+0

Bueno, parecías colgado al decir que no podías sobrecargar [] [] porque no era un operador real. No me interesaba sobrecargar [] [], tanto como solo quería poder indexar cosas usando [] [] para tipos personalizados, sin anidar. Martin York y Matteo Italia proporcionaron esto. Sus respuestas son técnicamente correctas para la "T", pero necesita pensar un poco más fuera de la caja; en cuyo caso sus respuestas serían más útiles. –

0

Cubrí el operador de sobrecarga [] para una matriz multidimensional en un answer to a previous question.

Probablemente trataré con iteradores de forma muy similar: Tengo un iterador que representa un "corte" (fila o columna) de la matriz multidimensional, y luego otro que representa un elemento en esa porción.

+0

Su respuesta parece provenir de aquí: http: // www. parashift.com/c++-faq-lite/operator-overloading.html#faq-13.10 Sin embargo, eso no responde mi pregunta. ¿Hay alguna forma de usar [] []. Aceptaré una respuesta que transforme mi clase en una anidada. Sin embargo, en un malloc malo, todas las instancias de VectorDeque deben cambiar a Deque, no solo esa ... –

+1

@Jason: En realidad, no. La respuesta en las preguntas frecuentes proviene de antiguas publicaciones de Usenet, algunas de ellas de mí. Por ejemplo, Robert Martin y yo proporcionamos las dos respuestas en un hilo de 1996, que creo que es anterior a que se incluya en las preguntas frecuentes: http://groups.google.com/group/comp.lang.c++moderated/browse_frm/ thread/0a52d34d173dd27d/298b8a42e13f4986 –

Cuestiones relacionadas