2010-05-26 9 views
5

Estoy intentando escribir una clase que contiene varios std :: vectores como miembros de datos, y proporciona un subconjunto de la interfaz de vector para acceder a ellos:STLifying C++ clases

class Mesh 
{ 
public: 
private: 
    std::vector<Vector3> positions; 
    std::vector<Vector3> normals; 
    // Several other members along the same lines 
}; 

Lo más importante que puede hacer con una malla es agregarle posiciones, normales y otras cosas. Con el fin de permitir una manera similar a STL de acceder a una malla (agregar de matrices, otros recipientes, etc.), estoy jugando con la idea de los métodos de la adición de la siguiente manera:

public: 
    template<class InIter> 
    void AddNormals(InIter first, InIter last); 

El problema es que, de lo Entiendo las plantillas, estos métodos deberán definirse en el archivo de encabezado (parece tener sentido, sin un tipo de iterador concreto, el compilador no sabe cómo generar código objeto para la implementación obvia de este método).

  1. ¿Esto es realmente un problema? Mi reacción visceral no es andar pegando grandes cantidades de código en los archivos de encabezado, pero mi C++ está un poco oxidado con poca experiencia de STL fuera de los ejemplos de juguetes, y no estoy seguro de qué práctica de codificación C++ "aceptable" tiene esto.

  2. ¿Existe alguna forma mejor de exponer esta funcionalidad conservando un sabor genérico de programación tipo STL? Una forma sería algo como esto:

(lista final)

class RestrictedVector<T> 
{ 
public: 
    RestrictedVector(std::vector<T> wrapped) 
    : wrapped(wrapped) {} 

    template <class InIter> 
    void Add(InIter first, InIter last) 
    { 
    std::copy(first, last, std::back_insert_iterator(wrapped)); 
    } 

private: 
    std::vector<T> wrapped; 
}; 

y luego exponer los casos de estos en malla en su lugar, pero eso está empezando a oler un poco de manipulación excesiva: P Cualquier consejo es ¡apreciado enormemente!

Respuesta

5

estos métodos tendrán que ser definido en el archivo de cabecera

tienen que ser definido en un archivo cabecera, de modo que si se utilizan entonces están disponibles en la unidad de traducción, donde la función de plantilla está instanciada. Si le preocupan demasiadas plantillas en los archivos de encabezado, ralentizando la compilación de las unidades de traducción que usan Mesh pero no usan realmente esa función de plantilla, entonces podría mover la implementación a un archivo de encabezado separado.Hace que la vida sea un poco más complicada para los clientes, al decidir si incluir o no el encabezado de la clase "con mucha grasa", pero en realidad no es difícil.

Alternativamente, para este ejemplo en particular, podría definir un iterador de salida para Malla, que agrega Normales. A continuación, los clientes con sus iteradores arbitrarias pueden hacer:

std::copy(first, last, mymesh.normalAdder()); 

La única cabecera que necesitan con código de la plantilla en ella es <algorithm>, que muy posiblemente ya tienen.

para hacerlo usted mismo, el objeto devuelto por normalAdder() necesita sobrecargar operator++() y operator*(), que a su vez tiene que devolver un objeto proxy (por lo general *this) que implementa operator=(const &Vector3). Eso se agrega al vector de normales. Pero todo eso no es un código de plantilla, y puede implementarse en su archivo .cpp.

De nuevo en este ejemplo, normalAdder() podría simplemente devolver std::back_inserter(this.normals);, una plantilla de <iterator>.

En cuanto a si tiene que preocuparse por ello, creo que cuando los tiempos de compilación van hacia el cielo, es más frecuente debido a dependencias innecesarias en lugar de debido a pequeños bits de código de plantilla en los encabezados. Algunos proyectos grandes parecen necesitar medidas drásticas, pero personalmente no he trabajado con más de 100 archivos más o menos.

+0

Dos candidatos para soluciones, ambos usando lo que parecen ideas muy específicas de C++ :) Muchas gracias, eso es enormemente útil. – shambulator

0
  1. ¿Esto es realmente un problema? Mi reacción visceral no es dar la vuelta pegue enormes trozos de código en la cabecera archivos, pero mi C++ es un poco oxidado con no mucha experiencia STL fuera ejemplos de juguete, y no estoy seguro de lo que C "aceptable" ++ codificación la práctica es en esto.

Me hago la pregunta ¿Necesita tener tan genérica-dad? Recuerde que STL fue escrito para ser extremadamente genérico para las necesidades de todos. Su código es para usted y su equipo, para resolver un problema muy específico. Una interfaz no genérica, específica de un problema funcionaría bien y sería más clara para las personas en su equipo/dominio del problema.

De lo contrario ... lo que ha especificado es excelente si necesita ese nivel de genérico. Ha permitido que se acepte cualquier tipo de iterador como argumento. No hay nada de malo con lo que tienes a primera vista. Esto puede ser muy útil.

+0

¡Gracias por la respuesta! Es discutible si esto tiene que ser tan genérico como lo estoy buscando, pero una gran parte de mi motivación para este proyecto es resucitar y modernizar mis habilidades en C++, tristemente descuidado desde la universidad. – shambulator

2

Yo diría que solo muerde la bala y crea un API/encabezado limpio y legible.

La idea de Steve de devolver un iterador de salida es inteligente, pero va a ser contraintuitivo para los clientes de su clase. Cuando alguien quiere agregar algunas normales, estará pensando "¿dónde está el método para agregar normales?", No "cómo obtengo un iterador de salida normal". (A menos que esté en un muy tienda pro-STL)

El requisito de definir la implementación de métodos con plantilla en el encabezado se puede mitigar un tanto moviéndolos fuera de la declaración de clase.

class Mesh 
{ 
    public: 
     void AddPosition  (Vector3 const & position); 
     void AddNormal  (Vector3 const & normal); 

     template< typename InIter > 
     void AddPositions (InIter const begin, InIter const end); 

     template< typename InIter > 
     void AddNormals  (InIter const begin, InIter const end); 

    private: 
     std::vector<Vector3> positions; 
     std::vector<Vector3> normals; 
}; 

template< typename InIter > 
void 
Mesh::AddPositions<InIter>(InIter const begin, InIter const end) 
{ 
    positions.insert(positions.end(), begin, end); 
} 

template< typename InIter > 
void 
Mesh::AddNormals<InIter>(InIter const begin, InIter const end) 
{ 
    normals.insert(normals.end(), begin, end); 
}