Typedefs internos en C++: ¿buen estilo o mal estilo?

9 minutos de lectura

Algo que me he encontrado haciendo a menudo últimamente es declarar typedefs relevantes para una clase particular dentro de esa clase, es decir

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

Estos tipos se utilizan luego en otras partes del código:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Razones por las que me gusta:

  • Reduce el ruido introducido por las plantillas de clase, std::vector<Lorem> se convierte Lorem::vectoretc.
  • Sirve como una declaración de intenciones: en el ejemplo anterior, la clase Lorem está destinada a ser contada por referencia a través de boost::shared_ptr y se almacena en un vector.
  • Permite que la implementación cambie, es decir, si es necesario cambiar Lorem para que se cuente de forma intrusiva la referencia (a través de boost::intrusive_ptr) en una etapa posterior, esto tendría un impacto mínimo en el código.
  • Creo que se ve ‘más bonito’ y posiblemente sea más fácil de leer.

Razones por las que no me gusta:

  • A veces hay problemas con las dependencias: si desea incrustar, digamos, un Lorem::vector dentro de otra clase, pero solo necesita (o quiere) reenviar declarar Lorem (en lugar de introducir una dependencia en su archivo de encabezado), entonces termina teniendo que usar los tipos explícitos (por ejemplo, boost::shared_ptr<Lorem> más bien que Lorem::ptr), lo cual es un poco inconsistente.
  • Puede que no sea muy común y, por lo tanto, más difícil de entender.

Trato de ser objetivo con mi estilo de codificación, por lo que sería bueno obtener otras opiniones al respecto para poder analizar un poco mi pensamiento.

Creo que es un estilo excelente, y yo mismo lo uso. Siempre es mejor limitar el alcance de los nombres tanto como sea posible, y el uso de clases es la mejor forma de hacerlo en C++. Por ejemplo, la biblioteca estándar de C++ hace un uso intensivo de typedefs dentro de las clases.

  • Ese es un buen punto, me pregunto si se veía ‘más bonito’ porque mi subconsciente señaló delicadamente que el alcance limitado es un bueno cosa. Sin embargo, me pregunto, ¿el hecho de que STL lo use predominantemente en las plantillas de clase lo convierte en un uso sutilmente diferente? ¿Es más difícil de justificar en una clase ‘concreta’?

    – Will Baker

    17 de abril de 2009 a las 8:55

  • Bueno, la biblioteca estándar se compone de plantillas en lugar de clases, pero creo que la justificación es la misma para ambos.

    luego

    17 de abril de 2009 a las 9:38

avatar de usuario
Marc Mutz – mmutz

Sirve como una declaración de intenciones: en el ejemplo anterior, la clase Lorem está destinada a contarse como referencia a través de boost::shared_ptr y almacenarse en un vector.

Esto es exactamente lo que hace no hacer.

Si veo ‘Foo::Ptr’ en el código, no tengo ni idea de si es shared_ptr o Foo* (STL tiene ::pointer typedefs que son T*, recuerda) o lo que sea. Esp. si es un puntero compartido, no proporciono un typedef en absoluto, pero mantengo el uso shared_ptr explícitamente en el código.

En realidad, casi nunca uso typedefs fuera de la metaprogramación de plantillas.

El STL hace este tipo de cosas todo el tiempo

El diseño STL con conceptos definidos en términos de funciones miembro y typedefs anidados es un callejón sin salida histórico, las bibliotecas de plantillas modernas usan funciones gratuitas y clases de características (cf. Boost.Graph), porque no excluyen los tipos integrados de modelar el concepto y porque facilita la adaptación de tipos que no fueron diseñados teniendo en cuenta los conceptos de las bibliotecas de plantillas dadas.

No uses el STL como una razón para cometer los mismos errores.

  • Estoy de acuerdo con tu primera parte, pero tu edición reciente es un poco miope. Dichos tipos anidados simplifican la definición de clases de rasgos, ya que proporcionan un valor predeterminado razonable. Considere el nuevo std::allocator_traits<Alloc> clase… no tiene que especializarlo para cada asignador que escriba, porque simplemente toma prestados los tipos directamente de Alloc.

    –Dennis Zickefoose

    11 de noviembre de 2011 a las 17:22

  • @Dennis: en C++, la conveniencia debe estar del lado del /usuario/ de una biblioteca, no del lado de su /autor/: el usuario desea una interfaz uniforme para un rasgo, y solo una clase de rasgo puede dar eso, por las razones expuestas anteriormente). Pero incluso como un Alloc autor, no es precisamente más difícil especializarse std::allocator_traits<> para su nuevo tipo que agregar los typedefs necesarios. También edité la respuesta, porque mi respuesta completa no encajaba en un comentario.

    – Marc Mutz – mmutz

    12 de noviembre de 2011 a las 14:11


  • Pero es del lado del usuario. Como un usuario de allocator_traits intentando crear un asignador personalizado, no tengo que molestarme con los quince miembros de la clase de rasgos… todo lo que tengo que hacer es decir typedef Blah value_type; y proporcionar las funciones miembro apropiadas, y el valor predeterminado allocator_traits averiguará el resto. Además, mire su ejemplo de Boost.Graph. Sí, hace un uso intensivo de la clase de rasgos… pero la implementación predeterminada de graph_traits<G> simplemente consultas G para sus propios typedefs internos.

    –Dennis Zickefoose

    12 de noviembre de 2011 a las 16:11


  • E incluso la biblioteca estándar 03 hace uso de clases de características cuando corresponde… la filosofía de la biblioteca no es operar en contenedores genéricamente, sino operar en iteradores. Por lo que proporciona un iterator_traits class para que sus algoritmos genéricos puedan consultar fácilmente la información adecuada. Lo cual, de nuevo, por defecto consulta al iterador por su propia información. En resumidas cuentas, los rasgos y las definiciones de tipo internas difícilmente se excluyen mutuamente… se apoyan entre sí.

    –Dennis Zickefoose

    12 de noviembre de 2011 a las 16:17

  • @Dennis: iterator_traits se hizo necesario porque T* debe ser un modelo de RandomAccessIteratorpero no puede poner los typedefs requeridos en T*. una vez que tuvimos iterator_traits, los typedefs anidados se volvieron redundantes, y desearía que se hubieran eliminado allí mismo. Por la misma razón (imposibilidad de agregar typedefs internos), T[N] no modela el STL Sequence concepto, y necesitas trucos como std::array<T,N>. Boost.Range muestra cómo se puede definir un concepto de Secuencia moderno que T[N] puede modelar, porque no requiere definiciones de tipo anidadas ni funciones miembro.

    – Marc Mutz – mmutz

    13 de noviembre de 2011 a las 22:14


Typedefs son los que diseño basado en políticas y rasgos construido sobre C++, por lo que el poder de la programación genérica en C++ se deriva de los typedefs mismos.

Los Typdefs definitivamente son de buen estilo. Y todas tus “razones por las que me gusta” son buenas y correctas.

Sobre los problemas que tienes con eso. Bueno, la declaración directa no es un santo grial. Simplemente puede diseñar su código para evitar dependencias de varios niveles.

Puede mover typedef fuera de la clase, pero Class::ptr es mucho más bonito que ClassPtr que no hago esto. Es como con los espacios de nombres para mí: las cosas permanecen conectadas dentro del alcance.

A veces lo hice

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

Y puede ser predeterminado para todas las clases de dominio y con alguna especialización para ciertas.

STL hace este tipo de cosas todo el tiempo: los typedefs son parte de la interfaz para muchas clases en STL.

reference
iterator
size_type
value_type
etc...

son todos typedefs que son parte de la interfaz para varias clases de plantillas STL.

  • Cierto, y sospecho que aquí es donde lo recogí por primera vez. Sin embargo, parece que estos serían un poco más fáciles de justificar. No puedo evitar ver los typedefs dentro de una plantilla de clase como más parecidos a las variables, si piensas en la línea de ‘metaprogramación’.

    – Will Baker

    17 de abril de 2009 a las 8:47

avatar de usuario
keithb

Otro voto para que sea una buena idea. Empecé a hacer esto cuando escribía una simulación que tenía que ser eficiente, tanto en el tiempo como en el espacio. Todos los tipos de valor tenían una definición de tipo Ptr que comenzaba como un puntero compartido de refuerzo. Luego hice algunos perfiles y cambié algunos de ellos a un puntero intrusivo de refuerzo sin tener que cambiar nada del código donde se usaron estos objetos.

Tenga en cuenta que esto solo funciona cuando sabe dónde se van a usar las clases y que todos los usos tienen los mismos requisitos. No usaría esto en el código de la biblioteca, por ejemplo, porque al escribir la biblioteca no se puede saber el contexto en el que se usará.

  • Cierto, y sospecho que aquí es donde lo recogí por primera vez. Sin embargo, parece que estos serían un poco más fáciles de justificar. No puedo evitar ver los typedefs dentro de una plantilla de clase como más parecidos a las variables, si piensas en la línea de ‘metaprogramación’.

    – Will Baker

    17 de abril de 2009 a las 8:47

avatar de usuario
Bodo

Actualmente estoy trabajando en el código, que utiliza de forma intensiva este tipo de typedefs. Hasta ahora eso está bien.

Pero me di cuenta de que a menudo hay typedefs iterativos, las definiciones se dividen entre varias clases, y nunca sabes realmente con qué tipo estás tratando. Mi tarea es resumir el tamaño de algunas estructuras de datos complejas ocultas detrás de estos typedefs, por lo que no puedo confiar en las interfaces existentes. En combinación con tres a seis niveles de espacios de nombres anidados y luego se vuelve confuso.

Entonces, antes de usarlos, hay algunos puntos a considerar

  • ¿Alguien más necesita estos typedefs? ¿La clase es muy utilizada por otras clases?
  • ¿Acorto el uso u oculto la clase? (En caso de ocultarse, también podría pensar en interfaces).
  • ¿Hay otras personas trabajando con el código? ¿Cómo lo hicieron? ¿Pensarán que es más fácil o se confundirán?

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad