c++ coding-style

Usando "super" en C++



c# c++ c (17)

A menudo lo he visto usado, a veces como super_t, cuando la base es un tipo de plantilla compleja ( boost::iterator_adaptor hace esto, por ejemplo)

Mi estilo de codificación incluye el siguiente modismo:

class Derived : public Base { public : typedef Base super; // note that it could be hidden in // protected/private section, instead // Etc. } ;

Esto me permite usar "super" como un alias para Base, por ejemplo, en constructores:

Derived(int i, int j) : super(i), J(j) { }

O incluso cuando se llama al método desde la clase base dentro de su versión anulada:

void Derived::foo() { super::foo() ; // ... And then, do something else }

Incluso puede estar encadenado (aún tengo que encontrar el uso para eso):

class DerivedDerived : public Derived { public : typedef Derived super; // note that it could be hidden in // protected/private section, instead // Etc. } ; void DerivedDerived::bar() { super::bar() ; // will call Derived::bar super::super::bar ; // will call Base::bar // ... And then, do something else }

De todos modos, creo que el uso de "typedef super" es muy útil, por ejemplo, cuando Base es verboso y / o templado.

El hecho es que super se implementa en Java, así como en C # (donde se llama "base", a menos que esté equivocado). Pero C ++ carece de esta palabra clave.

Entonces, mis preguntas:

  • ¿Es este uso de typedef supercomún / raro / nunca visto en el código con el que trabajas?
  • ¿Es este uso de typedef super Ok (es decir, ¿ves razones fuertes o no tan fuertes para no usarlo)?
  • ¿debería ser "super" algo bueno, debería estar algo estandarizado en C ++, o es este uso a través de un typedef suficiente?

Editar: Roddy mencionó el hecho de que el typedef debería ser privado. Esto significa que cualquier clase derivada no podrá usarlo sin volver a definirlo. Pero supongo que también evitaría el súper :: súper encadenamiento (¿pero quién va a llorar por eso?).

Edit 2: Ahora, algunos meses después de usar masivamente "super", estoy totalmente de acuerdo con el punto de vista de Roddy: "super" debe ser privado. Resumiría su respuesta dos veces, pero creo que no puedo.


Acabo de encontrar una solución alternativa. Tengo un gran problema con el enfoque typedef que me mordió hoy:

  • El typedef requiere una copia exacta del nombre de clase. Si alguien cambia el nombre de la clase pero no cambia el typedef, entonces se encontrará con problemas.

Así que se me ocurrió una solución mejor usando una plantilla muy simple.

template <class C> struct MakeAlias : C { typedef C BaseAlias; };

Entonces ahora, en lugar de

class Derived : public Base { private: typedef Base Super; };

tienes

class Derived : public MakeAlias<Base> { // Can refer to Base as BaseAlias here };

En este caso, BaseAlias no es privado y he intentado evitar el uso descuidado seleccionando un nombre de tipo que debería alertar a otros desarrolladores.


Bjarne Stroustrup menciona en Diseño y Evolución de C ++ que el Comité de Estándares ISO C ++ consideró super como palabra clave la primera vez que se estandarizó C ++.

Dag Bruck propuso esta extensión, llamando a la clase base "heredada". La propuesta mencionaba el problema de la herencia múltiple y habría marcado usos ambiguos. Incluso Stroustrup estaba convencido.

Después de la discusión, Dag Bruck (sí, la misma persona que hace la propuesta) escribió que la propuesta era implementable, técnicamente sólida, y sin fallas importantes, y manejaba herencia múltiple. Por otro lado, no había suficiente inversión por dinero, y el comité debería manejar un problema más espinoso.

Michael Tiemann llegó tarde, y luego demostró que un superdefinido funcionaría bien, utilizando la misma técnica que se le preguntó en este post.

Entonces, no, esto probablemente nunca se estandarizará.

Si no tiene una copia, Design y Evolution vale la pena cubrir el precio. Se pueden obtener copias usadas por alrededor de $ 10.


Después de migrar de Turbo Pascal a C ++ en el pasado, solía hacer esto para tener un equivalente para la palabra clave "heredada" de Turbo Pascal, que funciona de la misma manera. Sin embargo, después de programar en C ++ durante unos años, dejé de hacerlo. Descubrí que no lo necesitaba mucho.


Este es un método que uso que usa macros en lugar de un typedef. Sé que esta no es la forma C ++ de hacer las cosas, pero puede ser conveniente cuando encadenar iteradores juntos a través de la herencia cuando solo la clase base más abajo en la jerarquía actúa sobre un desplazamiento heredado.

Por ejemplo:

// some header.h #define CLASS some_iterator #define SUPER_CLASS some_const_iterator #define SUPER static_cast<SUPER_CLASS&>(*this) template<typename T> class CLASS : SUPER_CLASS { typedef CLASS<T> class_type; class_type& operator++(); }; template<typename T> typename CLASS<T>::class_type CLASS<T>::operator++( int) { class_type copy = *this; // Macro ++SUPER; // vs // Typedef // super::operator++(); return copy; } #undef CLASS #undef SUPER_CLASS #undef SUPER

La configuración genérica que utilizo hace que sea muy fácil leer y copiar / pegar entre el árbol de herencia que tiene código duplicado, pero debe ser anulado porque el tipo de retorno debe coincidir con la clase actual.

Uno podría usar un super minúscula para replicar el comportamiento visto en Java, pero mi estilo de codificación es usar todas las letras mayúsculas para macros.


FWIW Microsoft ha agregado una extensión para __super en su compilador.


He visto este modismo empleado en muchos códigos y estoy bastante seguro de que incluso lo he visto en alguna parte de las bibliotecas de Boost. Sin embargo, por lo que recuerdo, el nombre más común es base (o Base ) en lugar de super .

Esta expresión es especialmente útil si se trabaja con clases de plantilla. Como ejemplo, considere la siguiente clase (de un proyecto real ):

template <typename TText, typename TSpec> class Finder<Index<TText, PizzaChili<TSpec> >, PizzaChiliFinder> : public Finder<Index<TText, PizzaChili<TSpec> >, Default> { typedef Finder<Index<TText, PizzaChili<TSpec> >, Default> TBase; // … }

No te preocupes por los nombres divertidos. El punto importante aquí es que la cadena de herencia utiliza argumentos de tipo para lograr el polimorfismo en tiempo de compilación. Desafortunadamente, el nivel de anidación de estas plantillas es bastante alto. Por lo tanto, las abreviaturas son cruciales para la legibilidad y la mantenibilidad.


Intentaba resolver exactamente este mismo problema; Lancé algunas ideas, como el uso de plantillas variadic y la expansión del paquete para permitir un número arbitrario de padres, pero me di cuenta de que eso daría como resultado una implementación como ''super0'' y ''super1''. Lo arruiné porque eso sería apenas más útil que no tenerlo para empezar.

Mi solución implica una clase auxiliar PrimaryParent y se implementa de la siguiente manera:

template<typename BaseClass> class PrimaryParent : virtual public BaseClass { protected: using super = BaseClass; public: template<typename ...ArgTypes> PrimaryParent<BaseClass>(ArgTypes... args) : BaseClass(args...){} }

Entonces, cualquiera que sea la clase que quiera usar se declarará como tal:

class MyObject : public PrimaryParent<SomeBaseClass> { public: MyObject() : PrimaryParent<SomeBaseClass>(SomeParams) {} }

Para evitar la necesidad de usar herencia virtual en PrimaryParent en BaseClass , se BaseClass un constructor que toma una cantidad variable de argumentos para permitir la construcción de BaseClass .

La razón detrás de la herencia public de BaseClass en PrimaryParent es permitir que MyObject tenga control total sobre la herencia de BaseClass pesar de tener una clase de ayuda entre ellos.

Esto significa que cada clase que desee tener super debe usar la clase auxiliar PrimaryParent , y cada elemento secundario solo puede heredar de una clase utilizando PrimaryParent (de ahí el nombre).

Otra restricción para este método es que MyObject solo puede heredar una clase que hereda de PrimaryParent , y esa debe ser heredada usando PrimaryParent . Esto es lo que quiero decir:

class SomeOtherBase : public PrimaryParent<Ancestor>{} class MixinClass {} //Good class BaseClass : public PrimaryParent<SomeOtherBase>, public MixinClass {} //Not Good (now ''super'' is ambiguous) class MyObject : public PrimaryParent<BaseClass>, public SomeOtherBase{} //Also Not Good (''super'' is again ambiguous) class MyObject : public PrimaryParent<BaseClass>, public PrimaryParent<SomeOtherBase>{}

Antes de descartar esto como una opción debido a la aparente cantidad de restricciones y al hecho de que existe una clase de intermediario entre cada herencia, estas cosas no son malas.

La herencia múltiple es una herramienta sólida, pero en la mayoría de los casos, solo habrá un padre primario, y si hay otros padres, probablemente sean clases Mixin o clases que no hereden de PrimaryParent todos modos. Si aún es necesaria la herencia múltiple (aunque muchas situaciones se beneficiarían de utilizar la composición para definir un objeto en lugar de la herencia), entonces simplemente define explícitamente super en esa clase y no hereda de PrimaryParent .

La idea de tener que definir super en cada clase no es muy atractiva para mí, ya que PrimaryParent permite super , claramente un alias basado en herencia, permanecer en la línea de definición de clase en lugar del cuerpo de clase donde deberían ir los datos.

Aunque podría ser solo yo.

Por supuesto, cada situación es diferente, pero considere estas cosas que he dicho al decidir qué opción usar.


No recuerdo haber visto esto antes, pero a primera vista me gusta. Como señala Ferruccio , no funciona bien ante MI, pero MI es más la excepción que la regla y no hay nada que diga que algo debe ser útil en todas partes para ser útil.


No sé si es raro o no, pero ciertamente he hecho lo mismo.

Como se ha señalado, la dificultad de hacer esta parte del lenguaje en sí es cuando una clase utiliza la herencia múltiple.


Súper (o heredado) es algo muy bueno porque si necesitas pegar otra capa de herencia entre Base y Derivada, solo tienes que cambiar dos cosas: 1. la "clase Base: foo" y 2. el typedef

Si no recuerdo mal, el comité de estándares de C ++ estaba considerando agregar una palabra clave para esto ... hasta que Michael Tiemann señaló que este truco tipodef funciona.

En cuanto a la herencia múltiple, dado que está bajo el control del programador, puede hacer lo que quiera: tal vez super1 y super2, o lo que sea.


Siempre he usado "heredado" en lugar de súper. (Probablemente debido a un fondo Delphi), y siempre lo hago de forma privada , para evitar el problema cuando el ''heredado'' se omite erróneamente de una clase pero una subclase intenta usarlo.

class MyClass : public MyBase { private: // Prevents erroneous use by other classes. typedef MyBase inherited; ...

Mi "plantilla de código" estándar para crear nuevas clases incluye typedef, por lo que tengo pocas oportunidades de omitirlo accidentalmente.

No creo que la sugerencia encadenada de "super :: super" sea una buena idea. Si lo haces, probablemente estés ligado a una jerarquía en particular, y cambiarla probablemente romperá las cosas.


Un problema con esto es que si olvida (re) definir super para las clases derivadas, entonces cualquier llamada a super :: something se compilará bien, pero probablemente no llame a la función deseada.

Por ejemplo:

class Base { public: virtual void foo() { ... } }; class Derived: public Base { public: typedef Base super; virtual void foo() { super::foo(); // call superclass implementation // do other stuff ... } }; class DerivedAgain: public Derived { public: virtual void foo() { // Call superclass function super::foo(); // oops, calls Base::foo() rather than Derived::foo() ... } };

(Como señaló Martin York en los comentarios a esta respuesta, este problema puede eliminarse haciendo que el typedef sea privado en lugar de público o protegido).


Una razón adicional para usar un typedef para la superclase es cuando está usando plantillas complejas en la herencia del objeto.

Por ejemplo:

template <typename T, size_t C, typename U> class A { ... }; template <typename T> class B : public A<T,99,T> { ... };

En la clase B, sería ideal tener un typedef para A; de lo contrario, estarías atrapado repitiéndolo donde quisieras para hacer referencia a los miembros de A.

En estos casos, también puede funcionar con herencia múltiple, pero no tendría un typedef llamado ''super'', se llamaría ''base_A_t'' o algo así.

--jeffk ++


Yo uso esto de vez en cuando. Justo cuando me encuentro escribiendo la clase base escribo un par de veces, la reemplazaré con un typedef similar al tuyo.

Creo que puede ser un buen uso. Como dices, si tu clase base es una plantilla, puede guardar la escritura. Además, las clases de plantilla pueden tomar argumentos que actúen como políticas sobre cómo debería funcionar la plantilla. Puede cambiar el tipo de base sin tener que reparar todas sus referencias, siempre que la interfaz de la base siga siendo compatible.

Creo que el uso a través del typedef ya es suficiente. No puedo ver cómo se integrará en el lenguaje de todos modos porque la herencia múltiple significa que puede haber muchas clases base, por lo que puede escribirla como lo considere adecuado para la clase que lógicamente considera que es la clase base más importante.



¿Es este uso de typedef supercomún / raro / nunca visto en el código con el que trabajas?

Nunca he visto este patrón en particular en el código de C ++ con el que trabajo, pero eso no significa que no esté ahí.

¿Es este uso de typedef super Ok (es decir, ¿ves razones fuertes o no tan fuertes para no usarlo)?

No permite herencia múltiple (limpiamente, de todos modos).

¿debería ser "super" algo bueno, debería estar algo estandarizado en C ++, o es este uso a través de un typedef suficiente?

Por la razón citada anteriormente (herencia múltiple), no. La razón por la que ves "super" en los otros idiomas que enumeró es que solo admiten herencia única, por lo que no hay confusión en cuanto a a qué se refiere "super". Por supuesto, en esos idiomas ES útil, pero realmente no tiene un lugar en el modelo de datos de C ++.

Ah, y para su información: C ++ / CLI admite este concepto en forma de la palabra clave "super". Tenga en cuenta, sin embargo, que C ++ / CLI no admite herencia múltiple tampoco.