programacion - typedef struct c++
Header file mejores prácticas para typedefs (5)
Estoy utilizando shared_ptr y STL extensamente en un proyecto, y esto está llevando a tipos 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.
Esto es fácil de descifrar, 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 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.
- Common.h (poner todos juntos 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 un typedef. Esto parece peligroso ...
¿Hay mejores prácticas aquí? ¿Cómo aparecen en código real, cuando la reutilización, la legibilidad y la coherencia son primordiales?
Marqué esta wiki de la comunidad si otros quieren agregar opciones adicionales para la discusión.
Estoy usando shared_ptr y STL extensamente en un proyecto, y esto está llevando a tipos largos, propensos 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 empezar, 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 arrastrar tipos fuera de su alcance declarado:
namespace MON {
namespace Diddy {
class Foo;
} /* << Diddy */
/*...*/
typedef Diddy::Foo Diddy_Foo;
} /* << MON */
Existen excepciones para esto:
- un tipo privado completamente ecapsualted
- un tipo contenido dentro de un nuevo alcance
mientras estamos en ello, se debe evitar el using
de ámbitos de espacios de nombres y alias de espacios de nombres; califique el alcance si desea minimizar la futura actividad principal.
Esto es fácil de descifrar, 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 avanzado"). 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 te preocupes 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''.
Common.h (poner todos juntos 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.
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 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 ...
de hecho, puede declarar un typedef en el mismo ámbito varias veces (por ejemplo, en dos encabezados separados), eso no es un error.
declarar 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, crea una ''compilación de traducción'' que incluya el mundo: el compilador marcará las declaraciones de los tipos de tipos que no coinciden.
intentar escabullirse con tipos y / o reenvíos mínimos (que son lo suficientemente cercanos como para liberarlos en la compilación) no vale la pena el esfuerzo. a veces necesitarás un montón de soporte condicional para las declaraciones avanzadas: una vez que se define, es fácil (las bibliotecas stl son un buen ejemplo de esto) en el caso de que también seas delantero declarando la 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 un typedef. Esto parece peligroso ...
una subclase de std::vector
menudo se señala 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 realmente es tan significativa, probablemente debería usar 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;
};
}
¿Hay mejores prácticas aquí? ¿Cómo aparecen en código real, cuando la reutilización, la legibilidad y la coherencia son primordiales?
en última instancia, encontré un pequeño y conciso enfoque basado en paquetes lo mejor para reutilizarlo, reducir los tiempos de compilación y minimizar la dependencia.
+1 para documentar las convenciones typedef.
- Foo.h - ¿puedes detallar los problemas que tienes con eso?
- FooFwd.h - No los usaría en general, solo en "puntos calientes obvios". (Sí, los "puntos de conexión" son difíciles de determinar). No cambia las reglas de la OMI porque cuando se introduce un encabezado fwd, el tipo asociado def de foo.h se mueve allí.
- Common.h : genial para proyectos pequeños, pero no escala, estoy de acuerdo.
- Algún tipo de fantasía #define ... ¡POR FAVOR NO! ...
- Usa una subclase vectorial , no la mejora. Sin embargo, puedes usar la contención.
Entonces aquí las sugerencias preliminares (revisadas de esa otra pregunta ...)
Los encabezados de tipo estándar
<boost/shared_ptr.hpp>
,<vector>
etc. pueden ir a un encabezado precompilado / archivo de inclusión compartido para el proyecto. Esto no es malo. (Personalmente todavía los incluyo donde sea necesario, pero eso funciona además de ponerlos en el PCH).Si el contenedor es un detalle de implementación, los typedefs van donde se declara el contenedor (por ejemplo, miembros privados de la clase si el contenedor es un miembro privado de la clase)
Los tipos asociados (como
FooListPtr
) van a donde se declara Foo, si el tipo asociado es el uso principal del tipo. Eso es casi siempre cierto para algunos tipos, por ejemplo,shared_ptr
.Si
Foo
recibe un encabezado de declaración adelantada por separado, y el tipo asociado está bien con eso, también se mueve a FooFwd.h.Si el tipo solo está asociado con una interfaz particular (por ejemplo, un parámetro para un método público), va allí.
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.
Desafortunadamente con typedefs tienes que elegir entre las opciones no ideales para tus archivos de cabecera. 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.
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 los typedefs necesarios. En este caso, se utiliza unique_ptr
lugar de shared_ptr
, pero el uso debe ser similar. Se parece a esto:
// 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 solo se declaran de antemano (las definiciones de clase completas no son necesarias, por lo que no es necesario incluir el archivo de encabezado 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 a futuro solo necesitan escribirse una vez, y cualquier cambio posterior solo debe ocurrir en la declaración completa en el archivo de encabezado de la clase (y esto provocará que se recompilen menos archivos fuente debido a la reducción dependencias).
Finalmente, parece que esto 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 usa el 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 archivo fuente (.cpp) que realmente los use .
Me gustaría ir con un enfoque combinado de encabezados directos y una especie 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 sea demasiado problemático: los encabezados directos solo necesitan conocer un número muy limitado de tipos (¿uno?) Y, a veces, ni siquiera el tipo completo
Incluso podría intentar generar automáticamente los encabezados usando un script (esto se hace, por ejemplo, en SeqAn ) si realmente hay tantos encabezados.