2010-03-01 13 views
51

Estoy utilizando shared_ptr y STL extensamente en un proyecto, y esto está llevando a tipos demasiado largos, propensos a errores como shared_ptr< vector< shared_ptr<const Foo> > > (soy un programador ObjC por preferencia, donde los nombres largos son la norma, y ​​aún así es demasiado.) Sería mucho más claro, creo, llamar constantemente a este FooListPtr y documentar la convención de nomenclatura que "Ptr" significa shared_ptr y "List" significa vector de shared_ptr.Mejores prácticas de archivos de cabecera para typedefs

Esto es fácil de escribir, pero está causando dolores de cabeza con los encabezados. Parece que tengo varias opciones para definir FooListPtr:

  • Foo.h. Eso entrelaza todos los encabezados y crea serios problemas de compilación, por lo que no es un comienzo.
  • FooFwd.h ("encabezado avanzado"). Esto es lo que sugiere Effective C++, basado en iosfwd.h. Es muy consistente, pero la sobrecarga de mantener el doble de encabezados parece molesto en el mejor de los casos.
  • Común.h (poner todas juntas en un archivo). Esto mata la reutilización al entrelazar muchos tipos no relacionados. Ahora no puedes simplemente levantar un objeto y moverlo a otro proyecto. No es un principiante.
  • Algún tipo de fantasía #define mágica que typedef si aún no ha sido typedefed. Tengo una antipatía permanente por el preprocesador porque creo que hace que sea difícil para las personas nuevas asimilar el código, pero tal vez ....
  • Use una subclase vectorial en lugar de una typedef. Esto parece peligroso ...

¿Existen mejores prácticas aquí? ¿Cómo aparecen en código real, cuando la reutilización, la legibilidad y la coherencia son primordiales?

He marcado esta wiki de la comunidad si otros quieren agregar opciones adicionales para el debate.

+5

¿Puedo preguntar por qué esta pregunta es una wiki de la comunidad? –

+0

@Konrad, si hubiera otras propuestas, sugerí que se agreguen a la lista para que a los lectores posteriores les resulte más fácil ver las distintas opciones separadas de las respuestas en función de sus méritos. ¿Quizás la wiki de la comunidad se usa de manera diferente? –

+1

Y después de más investigaciones redescubrí lo que descubrí la última vez que hice clic en la wiki de la comunidad, que es que no tenía intención de hacer eso ... Espero haber aprendido la lección esta vez. –

Respuesta

6

Me gustaría ir con un enfoque combinado de encabezados forward y un tipo de encabezado common.h que es específico para su proyecto y solo incluye todos los encabezados de declaración directa y cualquier otra cosa que sea común y ligera.

Se queja de la sobrecarga de mantener el doble de encabezados, pero no creo que esto suponga demasiado problema: los encabezados hacia adelante generalmente solo necesitan conocer un número muy limitado de tipos (uno), y a veces ni siquiera el tipo completo.

Usted podría incluso intentar los encabezados utilizando un script de generación automática (esto se hace por ejemplo en SeqAn) si hay realmente que muchas cabeceras.

+0

Trabajo principalmente en código que utilizan varios proyectos, por lo que casi ninguno de estos es específico de un solo proyecto. Un único "common.h" hace que sea muy difícil para múltiples proyectos reutilizar componentes. –

+0

@Konrad: ¿SeqAn usa las bibliotecas boost y std? ¿Qué hace el guión que mencionas? –

+0

@ Benoît: SeqAn utiliza STL pero (desafortunadamente, en mi humilde opinión) no hay bibliotecas de Boost. En cuanto a la secuencia de comandos, solo analiza todos los archivos de encabezado (pero, a continuación, SeqAn es solo encabezados) y genera un archivo grande que contiene declaraciones de todos los tipos y funciones exportadas. –

1

Desafortunadamente con typedefs tiene que elegir entre las opciones no ideales para sus archivos de encabezado. Hay casos especiales en los que la opción uno (justo en el encabezado de la clase) funciona bien, pero parece que no funcionará para usted. También hay casos en los que la última opción funciona bien, pero generalmente es donde usa la subclase para reemplazar un patrón que involucra una clase con un único miembro de tipo std :: vector. Para su situación, utilizaría la solución de encabezado que declara hacia adelante. Hay tipeo extra y gastos generales, pero de lo contrario no sería C++, ¿verdad? Mantiene las cosas separadas, limpias y rápidas.

+0

Parece ser el enfoque más consistente y escalable. "De lo contrario, no sería C++ ..." Parece ser el camino. Soy un programador ObjC por lo general. La gente se queja de los nombres largos en ObjC, pero los encuentro muy cómodos y no me importan en absoluto. Pero en C++ siento que me paso la mitad del tiempo haciendo trabajos detallados, o bien hago que el código sea incoherente y difícil de mantener para evitar el tipeo extra. –

4

+1 para documentar las convenciones typedef.

  • foo.h - se puede detallar los problemas que tienen con eso?
  • FooFwd.h - No los usaría en general, solo en "puntos calientes obvios". (Sí, los "puntos de acceso" son difíciles de determinar). No cambia las reglas de IMO porque cuando se introduce un encabezado fwd, los tipos de defc asociados de foo.h se mueven allí.
  • Común.h - genial para proyectos pequeños, pero no escala, estoy de acuerdo. !
  • Una especie de fantasía # define ... POR FAVOR NO ...
  • Utilice una subclase de vectores - no significa que sea mejor. Puede usar contención, sin embargo.

Así que aquí las sugerencias prelimenary (revisada de esa otra pregunta ..)

  1. cabeceras de tipo estándar <boost/shared_ptr.hpp>, etc. <vector> puede entrar en un encabezado precompilado/compartidos incluyen archivos para el proyecto. Esto no está mal. (personalmente sigo incluyéndolos donde sea necesario, pero eso funciona además de ponerlos en el PCH.)

  2. Si el contenedor es un detalle de implementación, los typedefs van donde se declara el contenedor (p. Ej., Miembros privados de la clase el recipiente es un miembro de la clase privada)

  3. tipos asociados (como FooListPtr) ir a donde foo es declarated, si del tipo asociado es el uso principal de este tipo. Eso es casi siempre cierto para algunos tipos, p. shared_ptr.

  4. Si Foo obtiene un encabezado de declaración directa separado, y el tipo asociado está bien con eso, también se mueve a FooFwd.h.

  5. Si el tipo solo está asociado a una interfaz en particular (por ejemplo, un parámetro para un método público), va allí.

  6. Si el tipo se comparte (y no cumple con ninguno de los criterios anteriores), obtiene su propio encabezado. Tenga en cuenta que esto también significa obtener todas las dependencias.

Se siente "obvio" para mí, pero estoy de acuerdo en que no es bueno como estándar de codificación.

+0

buenos puntos en general, pero re # 1: no recomiendo incluir el lote de bibliotecas estándar en un encabezado común. la peor ofensa es la cantidad de datos estáticos privados y definiciones producidas. – justin

+0

@Justin - Definiciones - de acuerdo, pero para eso están los encabezados precompilados. pero datos estáticos privados? no hay casi ninguno en los encabezados estándar (como lo sería por unidad de traducción de todos modos). ¿O te refieres a los problemas con las primeras implementaciones de soporte de plantillas? A menos que sea un problema conocido y comprobado para su compilador, no tiene sentido apegarse a él. – peterchen

+0

no uso encabezados precompilados. justificación: no es lo suficientemente común, y a menudo utilizo compilaciones combinadas por lo que en muchos casos hay un archivo compilado por paquete. Además, los encabezados precompilados no se escalan particularmente bien en los sistemas de compilación distribuida. en cuanto a los datos estáticos, una declaración obvia que aparece son los flujos de entrada/salida (incluidos por 'iostream'). Perdí aproximadamente el 20% de algunos binarios * después * de eliminar las inclusiones superfluas de las bibliotecas estándar (medido utilizando una implementación relativamente moderna de las librerías estándar enviadas con gcc de Apple). los problemas obviamente se extienden más allá del tamaño binario. – justin

13

Estoy programando en un proyecto que parece que usa el método common.h. Funciona muy bien para ese proyecto.

Hay un archivo llamado ForwardsDecl.h que está en el encabezado precompilado y simplemente reenvía-declara todas las clases importantes y typedefs necesarios. En este caso, se usa unique_ptr en lugar de shared_ptr, pero el uso debe ser similar. Se ve así:

// Forward declarations 
class ObjectA; 
class ObjectB; 
class ObjectC; 

// List typedefs 
typedef std::vector<std::unique_ptr<ObjectA>> ObjectAList; 
typedef std::vector<std::unique_ptr<ObjectB>> ObjectBList; 
typedef std::vector<std::unique_ptr<ObjectC>> ObjectCList; 

Este código es aceptado por Visual C++ 2010 a pesar de que las clases son solamente prospectivas declaradas (las definiciones de clase completos no son necesarias para que no haya necesidad de incluir el archivo de cabecera de cada clase).No sé si eso es estándar y otros compiladores requerirán la definición de clase completa, pero es útil que no: otra clase (ObjectD) puede tener un ObjectAList como miembro, sin la necesidad de incluir ObjectA.h; esto puede realmente ayuda a reducir las dependencias de archivos de encabezado!

El mantenimiento no es particularmente un problema, porque las declaraciones hacia adelante solo necesitan escribirse una vez, y cualquier cambio subsiguiente solo tiene que ocurrir en la declaración completa en el archivo de cabecera de la clase (y esto disparará menos archivos fuente) recompilado debido a dependencias reducidas).

Finalmente, parece que se puede compartir entre proyectos (no lo he probado) porque incluso si un proyecto no declara realmente un ObjectA, no importa porque solo se reenvió y si no se declara Úselo al compilador no le importa. Por lo tanto, el archivo puede contener los nombres de las clases en todos los proyectos en los que se utiliza, y no importa si faltan algunos para un proyecto en particular. Todo lo que se requiere es que el encabezado de declaración completo necesario (por ejemplo, ObjectA.h) esté incluido en cualquier fuente (.cpp) archivos que realmente usan ellos.

+0

Esto es muy similar a cómo organicé mis bibliotecas C++. El objetivo del archivo de encabezado de declaraciones directas es que debe declarar todas las clases y tipos de punteros dentro de la biblioteca. De esta forma, solo necesitas uno de esos encabezados, no uno por clase. Consulte http://stackoverflow.com/questions/3935183/forward-declaration-include-on-top-of-declaration-include-classfwd-h-class-h/3935468#3935468 para obtener más detalles. –

2

estoy usando shared_ptr y STL ampliamente en un proyecto, y esto lleva a un exceso de largas tipos, propensas a errores como shared_ptr < vector < shared_ptr>> (soy un programador ObjC por preferencia, donde los nombres largos son la norma, y ​​aún así es demasiado.) Sería mucho más claro, creo, llamar constantemente a este FoolistPtr y documentar la convención de nomenclatura que "Ptr" significa shared_ptr y "List" significa vector de shared_ptr.

para principiantes, recomiendo usar buenas estructuras de diseño para el alcance (por ejemplo, espacios de nombres), así como nombres descriptivos, no abreviados para typedefs. FooListPtr es terriblemente corto, imo. nadie quiere adivinar lo que significa una abreviatura (o se sorprenderá al descubrir que Foo es const, shared, etc.), y nadie quiere alterar su código simplemente por colisiones de alcance.

También puede ayudar elegir un prefijo para typedefs en sus bibliotecas (así como otras categorías comunes).

también es una mala idea para arrastrar tipos fuera de su alcance declarado:

namespace MON { 
namespace Diddy { 
class Foo; 
} /* << Diddy */ 

/*...*/ 
typedef Diddy::Foo Diddy_Foo; 

} /* << MON */ 

hay excepciones a esto:

  • un tipo privado por completo ecapsualted
  • un tipo de contenido dentro de una nuevo alcance

mientras estamos en ello, using in Se deben evitar los ámbitos del espacio de nombres y los alias del espacio de nombres. Califique el alcance si desea minimizar la futura conservación.

Esto es fácil de escribir, pero está causando dolores de cabeza con los encabezados. Parece que tengo varias opciones para definir FoolistPtr:

Foo.h. Eso entrelaza todos los encabezados y crea serios problemas de compilación, por lo que no es un comienzo.

puede ser una opción para declaraciones que realmente dependen de otras declaraciones. lo que implica que necesita dividir paquetes, o hay una interfaz común y localizada para subsistemas.

FooFwd.h ("encabezado hacia adelante"). Esto es lo que sugiere C++ efectivo, basado en iosfwd.h. Es muy consistente, pero la sobrecarga de mantener el doble de encabezados parece molesto en el mejor de los casos.

no se preocupe por el mantenimiento de esto, realmente. es una buena practica el compilador usa declaraciones avanzadas y typedefs con muy poco esfuerzo. no es molesto porque ayuda a reducir sus dependencias y ayuda a garantizar que todas sean correctas y visibles. realmente no hay más que mantener, ya que los otros archivos hacen referencia al encabezado 'tipos de paquete'.

Común.h (poner todos juntos en un solo archivo). Esto mata la reutilización al entrelazar muchos tipos no relacionados. Ahora no puedes simplemente levantar un objeto y moverlo a otro proyecto. No es un principiante.

las dependencias e inclusiones basadas en paquetes son excelentes (ideal, realmente) - no descarta esto. obviamente tendrá que crear interfaces de paquetes (o bibliotecas) que estén bien diseñadas y estructuradas, y que representen clases de componentes relacionadas. estás haciendo un problema innecesario de la reutilización de objetos/componentes. minimice los datos estáticos de una biblioteca, y permita que las fases de enlace y franja hagan su trabajo. nuevamente, mantenga sus paquetes pequeños y reutilizables y esto no será un problema (suponiendo que sus bibliotecas/paquetes estén bien diseñados).

Algún tipo de fantasía #define magic that typedef's si aún no ha sido typedefed. Tengo una aversión permanente por el preprocesador, porque creo que hace que sea difícil para las personas nuevas que asimilen el código, pero tal vez ....

realidad, es posible declarar un typedef en el mismo ámbito varias veces (por ejemplo, , en dos encabezados separados) - eso no es un error.

declarando un typedef en el mismo ámbito con diferentes tipos subyacentes es un error. obviamente. debes evitar esto, y afortunadamente el compilador hace cumplir eso.

Para evitar esto, cree una 'compilación de traducción' que incluya el mundo: el compilador marcará las declaraciones de los tipos de tipos que no coinciden.

tratando de escabullirse con un mínimo de tipos y/o hacia delante (que son lo suficientemente cerca como para liberar en compilación) no vale la pena el esfuerzo. a veces necesitarás un montón de soporte condicional para las declaraciones hacia adelante; una vez que se define, es fácil (las bibliotecas stl son un buen ejemplo de esto) en el caso de que también declares hacia adelante template<typename,typename>class vector;).

es mejor tener todas estas declaraciones visibles para detectar cualquier error de inmediato, y puede evitar el preprocesador en este caso como una bonificación.

Use una subclase vectorial en lugar de una typedef. Esto parece peligroso ...

a menudo se señala una subclase de std::vector como un "error de principiante". este contenedor no estaba destinado a ser subclasificado. no recurra a malas prácticas simplemente para reducir sus tiempos de compilación/dependencias.si la dependencia es que realmente significativa, probablemente debería utilizar PIMPL, de todos modos:

// <package>.types.hpp 
namespace MON { 
class FooListPtr; 
} 

// FooListPtr.hpp 
namespace MON { 
class FooListPtr { 
    /* ... */ 
private: 
    shared_ptr< vector< shared_ptr<const Foo> > > d_data; 
}; 
} 

¿Existen mejores prácticas aquí? ¿Cómo aparecen en código real, cuando la reutilización, la legibilidad y la coherencia son primordiales?

En definitiva, he encontrado un pequeño y conciso enfoque basado en paquetes lo mejor para la reutilización, para reducir los tiempos de compilación y reducir al mínimo la dependencia.