El término "interfaz" en C ++

11

Java hace una clara distinción entre classy interface. (Creo que C # también lo hace, pero no tengo experiencia con eso). Sin embargo, al escribir C ++ no existe una distinción forzada por el lenguaje entre la clase y la interfaz.

En consecuencia, siempre he visto la interfaz como una solución para la falta de herencia múltiple en Java. Hacer tal distinción se siente arbitrario y sin sentido en C ++.

Siempre tendí a seguir el enfoque de "escribir cosas de la manera más obvia", por lo que si en C ++ tengo lo que podría llamarse una interfaz en Java, por ejemplo:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() = 0;
};

y luego decidí que la mayoría de los implementadores de Fooquería compartir alguna funcionalidad común que probablemente escribiría:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() {}
protected:
  // If it needs this to do its thing:
  int internalHelperThing(int);
  // Or if it doesn't need the this pointer:
  static int someOtherHelper(int);
};

Lo que hace que esto ya no sea una interfaz en el sentido de Java.

En cambio, C ++ tiene dos conceptos importantes, relacionados con el mismo problema de herencia subyacente:

  1. virtual inercia
  2. Las clases sin variables miembro no pueden ocupar espacio adicional cuando se usan como base

    "Los subobjetos de la clase base pueden tener un tamaño cero"

    Referencia

De los que trato de evitar # 1 siempre que sea posible, es raro encontrar un escenario en el que ese sea realmente el diseño "más limpio". Sin embargo, el n. ° 2 es una diferencia sutil pero importante entre mi comprensión del término "interfaz" y las características del lenguaje C ++. Como resultado de esto, actualmente (casi) nunca me refiero a cosas como "interfaces" en C ++ y hablo en términos de clases base y sus tamaños. Yo diría que en el contexto de C ++ "interfaz" es un nombre inapropiado.

Sin embargo, me ha llamado la atención que no mucha gente hace tal distinción.

  1. ¿Debo perder algo al permitir (por ejemplo protected) que no virtualexistan funciones dentro de una "interfaz" en C ++? (Mi sensación es exactamente lo contrario: una ubicación más natural para el código compartido)
  2. ¿Es significativo el término "interfaz" en C ++? ¿Implica solo puro virtualo sería justo llamar a las clases de C ++ sin variables miembro una interfaz todavía?
Flexografía
fuente
C # tiene la misma herencia múltiple de interfaces, herencia única de implementación que Java, pero a través del uso de métodos de extensión genéricos , internalHelperThingse puede simular en casi todos los casos.
Sjoerd
Tenga en cuenta que es una mala práctica exponer a los miembros virtuales.
Klaim
un público ~Foo() {}en una clase abstracta es un error en (casi) todas las circunstancias.
Wolf

Respuestas:

11

En C ++, el término "interfaz" no solo tiene una definición ampliamente aceptada, por lo que siempre que vaya a usarla, debe decir exactamente lo que quiere decir: una clase base virtual con o sin implementaciones predeterminadas, un archivo de encabezado, los miembros públicos de una clase arbitraria y así sucesivamente.

Con respecto a su ejemplo: en Java (y similar en C #), su código probablemente implicaría una separación de preocupaciones:

interface IFoo {/*  ... */} // here is your interface

class FooBase implements IFoo 
{
     // make default implementations for interface methods
}

class Foo extends FooBase
{
}

En C ++, puede hacer esto pero no está obligado a hacerlo. Y si desea llamar a una clase una interfaz si no tiene variables miembro, pero contiene implementaciones predeterminadas para algunos métodos, simplemente hágalo, pero asegúrese de que todas las personas con las que esté hablando sepan a qué se refiere.

Doc Brown
fuente
3
Otro posible significado de "interfaz", para un programador de C ++, son las publicpartes del .harchivo.
David Thornley
¿En qué idioma es su código de ejemplo? Si eso es un intento de ser C ++, estoy a punto de llorar ...
Qix - MONICA FUE MAL
@Qix: manténgalo fácil, lea mi publicación nuevamente (indica claramente que el código está en Java), y en caso de que obtenga> 2000 puntos, puede editar mi publicación para agregar el código de ejemplo C ++ equivalente, para hacer mi ejemplo más claro.
Doc Brown
Si eso es Java, entonces todavía está mal, y no, no puedo editarlo; No hay suficientes personajes para cambiar. Java no usa dos puntos en la implementación / extensión, razón por la cual me preguntaba si fue un intento de C ++ ...
Qix - MONICA FUE MAL
@ Qix: si encuentra más problemas sintácticos, puede guardarlos como regalo de Navidad :-)
Doc Brown
7

Parece un poco como si hubiera caído en la trampa de confundir el significado de lo que es que una interfaz es conceptualmente como una implementación (la interfaz - 'i' minúscula) y una abstracción (Una interfaz - 'I' mayúscula )

Con respecto a sus ejemplos, su primer bit de código es simplemente una clase. Si bien su clase tiene una interfaz en el sentido de que proporciona métodos para permitir el acceso a su comportamiento, no es una interfaz en el sentido de una declaración de interfaz que proporciona una capa de abstracción que representa un tipo de comportamiento que es posible que desee que las clases implementar. La respuesta de Doc Brown a su publicación muestra exactamente de lo que estoy hablando aquí.

Las interfaces a menudo se promocionan como la "solución" para los idiomas que no admiten la herencia múltiple, pero creo que es más una idea falsa que una verdad dura (¡y sospecho que me pueden criticar por decir eso!). Las interfaces realmente no tienen nada que ver con la herencia múltiple, ya que no requieren ser heredadas o ser un antepasado para proporcionar compatibilidad funcional entre clases o abstracción de implementación para las clases. De hecho, pueden permitirle eliminar por completo la herencia en caso de que desee implementar su código de esa manera, no es que lo recomiende por completo, pero solo digo que podríahazlo. Entonces, la realidad es que, independientemente de los asuntos de herencia, las interfaces proporcionan un medio por el cual se pueden definir los tipos de clase, estableciendo las reglas que determinan cómo los objetos pueden comunicarse entre sí, por lo que le permiten determinar el comportamiento que sus clases deberían soportar sin dictando el método específico utilizado para implementar ese comportamiento.

¿Debo perder algo al permitir que existan (por ejemplo, protegidas) funciones no virtuales dentro de una "interfaz" en C ++? (Mi sensación es exactamente lo contrario: una ubicación más natural para el código compartido)

Una interfaz pura está destinada a ser completamente abstracta, porque permite la definición de un contrato de compatibilidad entre clases que no necesariamente han heredado su comportamiento de un antepasado común. Cuando se implementa, desea elegir si permitir que el comportamiento de implementación se extienda en una subclase. Si sus métodos no lo son virtual, pierde la capacidad de extender ese comportamiento más tarde si decide que necesita crear clases descendientes. Independientemente de si la implementación es virtual o no, la interfaz define la compatibilidad de comportamiento en sí misma, mientras que la clase proporciona la implementación de ese comportamiento para las instancias que representa la clase.

¿Es significativo el término "interfaz" en C ++? ¿Implica solo virtual puro o sería justo llamar a las clases de C ++ sin variables miembro una interfaz todavía?

Perdóname, pero ha pasado mucho tiempo desde que realmente escribí una aplicación seria en C ++. Recuerdo que Interface es una palabra clave para fines de abstracción como la he descrito aquí. No llamaría a las clases de C ++ de ningún tipo una Interfaz, en cambio diría que la clase tiene una interfaz dentro de los significados que he descrito anteriormente. En ese sentido, el término ES significativo, pero eso realmente depende del contexto.

S.Robins
fuente
1
+1 para la distinción entre "tener" y "ser" una interfaz.
Doc Brown
6

Las interfaces Java no son una "solución", son una decisión de diseño deliberada para evitar algunos de los problemas con la herencia múltiple, como la herencia de diamantes, y para fomentar prácticas de diseño que minimicen el acoplamiento.

Su interfaz con métodos protegidos es un caso de libro de texto de la necesidad de "preferir la composición sobre la herencia". Para citar a Sutter y Alexandrescu en la sección con ese nombre de sus excelentes estándares de codificación C ++ :

Evite los impuestos a la herencia: la herencia es la segunda relación de acoplamiento más estrecha en C ++, solo superada por la amistad. El acoplamiento apretado no es deseable y debe evitarse siempre que sea posible. Por lo tanto, prefiera la composición a la herencia a menos que sepa que esta última realmente beneficia su diseño.

Al incluir sus funciones de ayuda en su interfaz, puede estar ahorrando un poco de escritura ahora, pero está introduciendo un acoplamiento que lo perjudicará en el futuro. Casi siempre es mejor a largo plazo hacer que las funciones de ayuda se separen y pasen a Foo*.

El STL es un muy buen ejemplo de esto. Se extraen tantas funciones auxiliares como sea posible en <algorithm>lugar de estar en las clases de contenedor. Por ejemplo, dado que sort()utiliza la API de contenedor público para hacer su trabajo, sabe que puede implementar su propio algoritmo de clasificación sin tener que cambiar ningún código STL. Este diseño permite bibliotecas como boost, que mejoran el STL en lugar de reemplazarlo, sin que el STL necesite saber nada sobre el boost.

Karl Bielefeldt
fuente
2

¿Debo perder algo al permitir que existan (por ejemplo, protegidas) funciones no virtuales dentro de una "interfaz" en C ++? (Mi sensación es exactamente lo contrario: una ubicación más natural para el código compartido)

Pensarías eso, pero poner un método protegido y no virtual en una clase abstracta determina la implementación a cualquiera que escriba una subclase. Hacerlo anula el propósito de una interfaz en su sentido puro, que es proporcionar un revestimiento que oculte lo que hay debajo.

Este es uno de esos casos en los que no hay una respuesta única para todos y tiene que usar su experiencia y criterio para tomar una decisión. Si puede decir con 100% de confianza que cada posible subclase de su clase Foo, que de otro modo sería completamente virtual , siempre necesitará una implementación del método protegido bar(), entonces Fooes el lugar adecuado para ello. Una vez que tenga una subclase Bazque no necesita bar(), tendrá que vivir con el hecho de que Baztendrá acceso al código que no debería o deberá realizar el ejercicio de reorganizar su jerarquía de clases. La primera no es una buena práctica y la segunda puede tomar más tiempo que los pocos minutos que hubiera tomado para organizar las cosas correctamente en primer lugar.

¿Es significativo el término "interfaz" en C ++? ¿Implica solo virtual puro o sería justo llamar a las clases de C ++ sin variables miembro una interfaz todavía?

La Sección 10.4 del estándar C ++ hace una mención pasajera del uso de clases abstractas para implementar interfaces, pero no las define formalmente. El término es significativo en un contexto informático general, y cualquier persona competente debe entender que " Fooes una interfaz para (lo que sea)" implica alguna forma de abstracción. Aquellos con exposición a idiomas con construcciones de interfaz definidas pueden pensar puramente virtuales, pero cualquiera que necesite trabajar realmente Fooverá su definición antes de continuar.

Blrfl
fuente
2
¿Cuál es la diferencia entre proporcionar una declaración virtual pura y una declaración más implementación? En ambos casos, tiene que haber una implementación, y bazsiempre puede tener una implementación como return false;o lo que sea. Si el método no se aplica a todas las subclases, no pertenece a la clase abstracta base de ninguna forma.
David Thornley
1
Creo que su última oración dice que estamos en la misma página: no hay ninguna razón por la que no pueda tener métodos protegidos en clases abstractas, pero no deberían estar más arriba en el árbol de herencia de lo absolutamente necesario. Solo creo que si una clase tiene que simular una implementación, no está en el lugar correcto en el árbol.
Blrfl