Pregunta ¿Qué tipo de cosas malas pasarían si escribes toda la clase en un solo archivo en C ++?


En C # o Java, las clases se declaran y definen al mismo tiempo. En C ++, la norma es hacer eso por separado. ¿Qué pasa si escribimos toda la clase en un archivo, por ejemplo, .cpp, e incluimos eso en los archivos que hacen referencia a él, qué tipo de cosas malas técnicamente sucederían además de un proceso de compilación prolongado?


11
2018-03-19 18:10


origen


Respuestas:


Si su implementación de MyClass está todo en el archivo de encabezado MyClass.h entonces cualquier archivo que necesitara implementar MyClass se incluirá cada vez que alguien incluya MyClass.h.

Si cambias alguna parte de MyClass.h, incluso si es trivial (como agregar un comentario o incluso un espacio), todos los archivos que lo incluyan deberán volver a compilarse, incluso si la interfaz no ha cambiado.

Ninguno de estos aspectos es importante para los proyectos de juguetes, pero como ha notado, cuando tiene un programa que consta de cientos (o miles, etc.) de archivos de clase, el tiempo de compilación agregado solo hace que valga la pena separar la implementación de la interfaz.

Por ejemplo, si tengo lo siguiente:

// MyClass.h
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>

#include "Inventory.h"

class MyClass
{
public:
  MyClass();

  void processInventory(Inventory& inventory)
  {
    // Do something with each item in the inventory here
    // that uses iostream, iomanip, sstream, and string
  }
private:
  // ...
};

Sería más ideomáticamente escrito como:

// MyClass.h
class Inventory;

class MyClass
{
public:
  MyClass();

  void processInventory(Inventory& inventory);
private:
  // ...
};

// MyClass.cc
#include "MyClass.h"

#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>

#include "Inventory.h"

MyClass()::MyClass()
{
}

void MyClass()::processInventory(Inventory& inventory)
{
  // Do something with each item in the inventory here
  // that uses iostream, iomanip, sstream, and string
}

Aviso: Incluyendo MyClass.h no significa iostream, iomanip, sstream, string, o Inventory.h tiene que ser analizado Cambiando cómo processInventory funciona no significa que todos los archivos que utilizan MyClass.h tiene que ser recompilado

Observe cuánto más fácil puede ser descubrir cómo usar MyClass ahora. Los archivos de encabezado cumplen una función importante: muestran a las personas cómo usar su clase. Con la modificación MyClass.h es fácil ver la lista de funciones. Si cada función está definida en el encabezado, entonces no puede ver solo la lista de funciones. Eso hace que sea más difícil descubrir cómo usar la clase.


12
2018-03-19 18:16



Puedes romper el una regla de definición.

Si escribes esto:

class foo
{
public:
    void doit();
};

foo::doit() {}

e incluir eso en múltiples clases, tendrás múltiples definiciones de foo::doit y su enlace fallará

Pero si haces todas tus clases en línea, ya sea definiéndolas dentro de la declaración de clase:

class foo
{
public:
    void doit() {
    }
};

o al hacerlos explícitamente en línea:

class foo
{
public:
    void doit();
};

inline void foo::doit() {}

entonces puedes incluir ese archivo tantas veces como quieras.


5
2018-03-19 18:19



El vinculador verá múltiples definiciones de los miembros de la clase cuando intente combinar múltiples objetos. Por lo tanto, no podrá generar un archivo binario a partir de archivos fuente que incluyan algo en más de un lugar.


2
2018-03-19 18:17



Normalmente se separa la declaración y la definición de una clase. Esto le permite usar su clase en diferentes archivos fuente simplemente incluyendo la declaración.

Si incluye un .cpp que tiene declaración y definición en 2 archivos fuente diferentes, entonces esa clase estará doblemente definida.

Cada .cpp en el que está incluida la clase compilará bien en archivos de objeto. Sin embargo, cada clase debe tener solo 1 definición total o, de lo contrario, no podrá vincular los archivos de objeto entre sí.


1
2018-03-19 18:14



Lo más importante para entender sobre #include en contraste con otros lenguajes importando métodos, es que #include COPIAS el contenido de ese archivo donde se coloca la directiva #include. Entonces, declarar y definir una clase en el mismo archivo creará tres cosas:

  • Aumente significativamente su compilación
    veces.

  • Si tus definiciones no están en línea obtendrá errores de enlazador, ya que el compilador encuentra múltiples
    definiciones a las mismas funciones

  • Eso expondría la implementación para el usuario, en lugar de solo la interfaz.

Es por eso que es una práctica común definir clases grandes en archivos separados, y en algunas ocasiones, clases muy pequeñas con implementaciones pequeñas (como punteros inteligentes) en un archivo (para también implícitamente métodos en línea).


1
2018-03-19 18:25



@Cuenta

Creo que es importante subrayar el punto de Bill:

Observe cuánto más fácil puede ser   Descubre cómo usar MyClass ahora.   Los archivos de encabezado sirven un importante   propósito: muestran a la gente cómo usar   Tu clase.

el archivo .h es más o menos el documento "público" para permitir la comprensión de cómo funciona tu clase en cierto modo conceptualmente: una interfaz. Recuerde que el archivo fuente debe considerarse de propiedad exclusiva. Recuerdo haber aprendido mucho sobre cómo funcionaba Unix en mis primeros días de C / C ++ al leer los archivos de encabezado. También recuerde que las complejidades de la función en línea no deberían ser más que accesorias


1
2018-03-19 20:11



Una gran razón para que una clase se defina en un archivo cpp es que no se necesita públicamente, solo es una función auxiliar (como, por ejemplo, un functor). Algunas personas parecen tener miedo de poner la clase completa en el archivo cpp, mientras que eso solo muestra su intención de usar solo la clase allí.


0
2018-03-19 20:43