Formas de organizar la interfaz y la implementación en C ++

12

He visto que hay varios paradigmas diferentes en C ++ con respecto a lo que entra en el archivo de encabezado y qué al archivo cpp. AFAIK, la mayoría de las personas, especialmente aquellas con antecedentes en C, hacen:

foo.h

 class foo {
 private:
     int mem;
     int bar();
 public:
     foo();
     foo(const foo&);
     foo& operator=(foo);
     ~foo();
 }

foo.cpp

 #include foo.h
 foo::bar() { return mem; }
 foo::foo() { mem = 42; }
 foo::foo(const foo& f) { mem = f.mem; }
 foo::operator=(foo f) { mem = f.mem; }
 foo::~foo() {}
 int main(int argc, char *argv[]) { foo f; }

Sin embargo, mis profesores suelen enseñar C ++ a principiantes como este:

foo.h

 class foo {
 private:
     int mem;
     int bar() { return mem; }
 public:
     foo() { mem = 42; }
     foo(const foo& f) { mem = f.mem; }
     foo& operator=(foo f) { mem = f.mem; }
     ~foo() {}
 }

foo.cpp

 #include foo.h
 int main(int argc, char* argv[]) { foo f; }
 // other global helper functions, DLL exports, and whatnot

Originario de Java, también siempre me he adherido a esta segunda vía por varias razones, como que solo tengo que cambiar algo en un lugar si cambian los nombres de la interfaz o del método, que me gusta la sangría diferente de las cosas en las clases cuando miro su implementación, y creo que los nombres son más legibles en foocomparación con foo::foo.

Quiero recolectar ventajas y desventajas de cualquier manera. ¿Quizás hay aún otras formas?

Una desventaja de mi manera es, por supuesto, la necesidad de declaraciones anticipadas ocasionales.

Felix Dombek
fuente
2
foo.cppahora no tiene nada que ver con su fooclase y debe dejarse vacío (tal vez #includepara que su sistema de compilación sea feliz).
Benjamin Bannier
2
Tus profesores están locos.
Carreras de ligereza en órbita el

Respuestas:

16

Si bien la segunda versión es más fácil de escribir, está mezclando la interfaz con la implementación.

Los archivos de origen que incluyen archivos de encabezado deben volver a compilarse cada vez que se cambian los archivos de encabezado. En la primera versión, cambiaría el archivo de encabezado solo si necesita cambiar la interfaz. En la segunda versión, cambiaría el archivo de encabezado si necesita cambiar la interfaz o la implementación.

Además de que no debe exponer detalles de implementación , obtendrá una compilación innecesaria con la segunda versión.

LennyProgrammers
fuente
1
+1 Mi generador de perfiles no instrumenta el código colocado en los archivos de encabezado; esa es una razón valiosa también.
Eugene
Si ves mi respuesta a esta pregunta programmers.stackexchange.com/questions/4573/… verás cómo eso depende mucho de la semántica de la clase, es decir, qué la usará (en particular si es una parte expuesta de su interfaz de usuario y cuántas otras clases en su sistema lo usan directamente).
CashCow
3

Lo hice por segunda vez en el '93-95. Tomó unos minutos para recompilar una pequeña aplicación con 5-10 funciones / archivos (en esa misma PC 486 ... y no, tampoco sabía nada de clases, solo tenía 14-15 años y no había Internet ) .

Entonces, lo que enseñas a los principiantes y lo que usas profesionalmente son técnicas muy diferentes, especialmente en C ++.

Creo que la comparación entre C ++ y un auto F1 es adecuada. No pones principiantes en un auto de F1 (que ni siquiera arranca a menos que precaliente el motor a 80-95 grados centígrados).

No enseñe C ++ como primer idioma. Debe tener la experiencia suficiente para saber por qué la opción 2 es peor que la opción 1 en general, saber un poco lo que significa la compilación / vinculación estática y, por lo tanto, comprender por qué C ++ lo prefiere de la primera manera.

Macke
fuente
Esta respuesta sería aún mejor si elaboraras un poco sobre compilación / vinculación estática (¡no lo sabía en ese entonces!)
Felix Dombek
2

El segundo método es lo que yo llamaría una clase totalmente en línea. Está escribiendo una definición de clase, pero todo el código que la use solo incluirá el código.

Sí, el compilador decide cuándo en línea y cuándo no ... Sin embargo, en este caso está ayudando al compilador a tomar una decisión, y potencialmente terminará generando menos código y potencialmente más rápido.

Es probable que esta ventaja supere el hecho de que si modifica la implementación de una función, necesita reconstruir toda la fuente que la utiliza. En la naturaleza ligera de la clase, no modificará la implementación. Si agrega un nuevo método, tendría que modificar el encabezado de todos modos.

Sin embargo, a medida que su clase se vuelve más compleja, incluso agregando un ciclo, el beneficio de hacerlo de esta manera disminuye.

Todavía tiene sus ventajas, en particular:

  • Si se trata de un código de "funcionalidad común", puede incluir el encabezado y usarlo desde varios proyectos sin tener que vincularlo con una biblioteca que contenga su fuente.

La desventaja de la inclusión en línea se convierte en un problema cuando significa que debe incorporar detalles de implementación en su encabezado, es decir, debe comenzar a incluir encabezados adicionales.

Tenga en cuenta que las plantillas son un caso especial, ya que prácticamente tiene que incluir los detalles de implementación. Puede ocultarlo en otro archivo pero debe estar allí. (Hay una excepción a esa regla con las instancias, pero en general usted alinea sus plantillas).

CashCow
fuente
1

Puede que no sea significativo o verdadero si su ejecutable se hace más grande, pero más código en los archivos de encabezado le da al compilador más oportunidades de optimizar la velocidad.

Si decide si desea escribir una biblioteca de solo encabezado , este tema es solo una de sus preocupaciones.

davidvandebunte
fuente