Recibo errores al intentar compilar una clase de plantilla C ++ que se divide entre un archivo .hpp
y .cpp
:
$ g++ -c -o main.o main.cpp
$ g++ -c -o stack.o stack.cpp
$ g++ -o main main.o stack.o
main.o: In function `main':
main.cpp:(.text+0xe): undefined reference to 'stack<int>::stack()'
main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()'
collect2: ld returned 1 exit status
make: *** [program] Error 1
Aquí está mi código:
stack.hpp :
#ifndef _STACK_HPP
#define _STACK_HPP
template <typename Type>
class stack {
public:
stack();
~stack();
};
#endif
stack.cpp :
#include <iostream>
#include "stack.hpp"
template <typename Type> stack<Type>::stack() {
std::cerr << "Hello, stack " << this << "!" << std::endl;
}
template <typename Type> stack<Type>::~stack() {
std::cerr << "Goodbye, stack " << this << "." << std::endl;
}
main.cpp :
#include "stack.hpp"
int main() {
stack<int> s;
return 0;
}
ld
es, por supuesto, correcto: los símbolos no están stack.o
.
La respuesta a esta pregunta no ayuda, ya que estoy haciendo lo que dice.
Este podría ayudar, pero no quiero mover todos y cada uno de los métodos al .hpp
archivo; no debería tener que hacerlo, ¿verdad?
¿Es la única solución razonable mover todo en el .cpp
archivo al .hpp
archivo y simplemente incluir todo, en lugar de vincularlo como un archivo de objeto independiente? ¡Eso parece terriblemente feo! En ese caso, yo también podría volver a mi estado anterior y el cambio de nombre stack.cpp
a stack.hpp
y hacerse con él.
Respuestas:
No es posible escribir la implementación de una clase de plantilla en un archivo cpp separado y compilar. Todas las formas de hacerlo, si alguien afirma, son soluciones para imitar el uso de un archivo cpp separado, pero prácticamente si tiene la intención de escribir una biblioteca de clases de plantilla y distribuirla con archivos de encabezado y lib para ocultar la implementación, simplemente no es posible .
Para saber por qué, veamos el proceso de compilación. Los archivos de encabezado nunca se compilan. Solo están preprocesados. El código preprocesado se combina con el archivo cpp que en realidad se compila. Ahora, si el compilador tiene que generar el diseño de memoria apropiado para el objeto, necesita conocer el tipo de datos de la clase de plantilla.
En realidad, debe entenderse que la clase de plantilla no es una clase en absoluto, sino una plantilla para una clase cuya declaración y definición es generada por el compilador en el momento de la compilación después de obtener la información del tipo de datos del argumento. Mientras no se pueda crear el diseño de la memoria, no se pueden generar las instrucciones para la definición del método. Recuerde que el primer argumento del método de clase es el operador 'this'. Todos los métodos de clase se convierten en métodos individuales con cambios de nombre y el primer parámetro como el objeto sobre el que opera. El argumento 'this' es el que realmente indica el tamaño del objeto que, en caso de que la clase de plantilla no esté disponible para el compilador, a menos que el usuario cree una instancia del objeto con un argumento de tipo válido. En este caso, si coloca las definiciones del método en un archivo cpp separado e intenta compilarlo, el archivo objeto en sí no se generará con la información de la clase. La compilación no fallará, generará el archivo de objeto pero no generará ningún código para la clase de plantilla en el archivo de objeto. Esta es la razón por la que el vinculador no puede encontrar los símbolos en los archivos de objeto y la compilación falla.
Ahora bien, ¿cuál es la alternativa para ocultar detalles importantes de implementación? Como todos sabemos, el objetivo principal detrás de separar la interfaz de la implementación es ocultar los detalles de la implementación en forma binaria. Aquí es donde debe separar las estructuras de datos y los algoritmos. Las clases de su plantilla deben representar solo estructuras de datos, no algoritmos. Esto le permite ocultar detalles de implementación más valiosos en bibliotecas de clases separadas sin plantilla, las clases dentro de las cuales funcionarían en las clases de plantilla o simplemente las usarían para almacenar datos. La clase de plantilla en realidad contendría menos código para asignar, obtener y configurar datos. El resto del trabajo lo realizarían las clases de algoritmos.
Espero que esta discusión sea útil.
fuente
Que es posible, siempre y cuando usted sabe lo ejemplificaciones que vas a necesidad.
Agregue el siguiente código al final de stack.cpp y funcionará:
Se crearán instancias de todos los métodos de pila que no sean de plantilla y el paso de enlace funcionará bien.
fuente
template class stack<int>;
.Puedes hacerlo de esta manera
Esto se ha discutido en Daniweb
También en las preguntas frecuentes pero usando la palabra clave de exportación C ++.
fuente
include
La creación de uncpp
archivo es generalmente una idea terrible. incluso si tiene una razón válida para esto, el archivo, que en realidad es solo un encabezado glorificado, debe recibir unahpp
extensión o alguna diferente (por ejemplotpp
) para dejar muy claro lo que está sucediendo, eliminar la confusión sobre los archivos que semakefile
dirigen a archivos realescpp
, etc..cpp
archivo es una idea terrible?cpp
(occ
, oc
, o lo que sea) indica que el archivo es una parte de la implementación, que la unidad de traducción resultante (salida del preprocesador) se puede compilar por separado y que el contenido del archivo se compila una sola vez. no indica que el archivo sea una parte reutilizable de la interfaz, para ser incluido arbitrariamente en cualquier lugar.#include
La creación de un archivo realcpp
llenaría rápidamente su pantalla con múltiples errores de definición, y con razón. en este caso, ya que no es una razón para#include
ello,cpp
era sólo la elección equivocada de extensión..cpp
extensión para tal uso. Pero usar otra palabra.tpp
está completamente bien, ¿cuál serviría para el mismo propósito pero usaría una extensión diferente para una comprensión más fácil / rápida?cpp
/cc
/ etc es necesario evitar, pero es una idea buena para usar algo distintohpp
- por ejemplotpp
,tcc
, etc - para que pueda volver a utilizar el resto del nombre de archivo e indicar que eltpp
archivo, aunque actúa como un encabezado, contiene la implementación fuera de línea de las declaraciones de plantilla en el correspondientehpp
. Entonces, esta publicación comienza con una buena premisa: separa las declaraciones y definiciones en 2 archivos diferentes, que pueden ser más fáciles de asimilar / grep o, a veces, se requieren debido a dependencias circulares IME, pero luego termina mal sugiriendo que el segundo archivo tiene una extensión incorrectaNo, no es posible. No sin la
export
palabra clave, que para todos los efectos no existe realmente.Lo mejor que puede hacer es poner las implementaciones de sus funciones en un archivo ".tcc" o ".tpp" y #incluya el archivo .tcc al final de su archivo .hpp. Sin embargo, esto es meramente cosmético; sigue siendo lo mismo que implementar todo en los archivos de encabezado. Este es simplemente el precio que paga por usar plantillas.
fuente
Creo que hay dos razones principales para intentar separar el código con plantilla en un encabezado y un cpp:
Uno es por mera elegancia. A todos nos gusta escribir código que sea fácil de leer, administrar y reutilizable más tarde.
Otro es la reducción de los tiempos de compilación.
Actualmente (como siempre) estoy codificando software de simulación en conjunto con OpenCL y nos gusta mantener el código para que pueda ejecutarse usando los tipos float (cl_float) o double (cl_double) según sea necesario, dependiendo de la capacidad de HW. Ahora mismo esto se hace usando un #define REAL al principio del código, pero esto no es muy elegante. Cambiar la precisión deseada requiere volver a compilar la aplicación. Dado que no hay tipos de tiempo de ejecución reales, tenemos que vivir con esto por el momento. Afortunadamente, los núcleos OpenCL se compilan en tiempo de ejecución, y un tamaño simple de (REAL) nos permite alterar el tiempo de ejecución del código del núcleo en consecuencia.
El problema mucho mayor es que a pesar de que la aplicación es modular, al desarrollar clases auxiliares (como las que precalculan las constantes de simulación) también hay que crear plantillas. Todas estas clases aparecen al menos una vez en la parte superior del árbol de dependencia de clases, ya que la clase de plantilla final Simulation tendrá una instancia de una de estas clases de fábrica, lo que significa que prácticamente cada vez que hago un cambio menor en la clase de fábrica, todo el software debe reconstruirse. Esto es muy molesto, pero parece que no puedo encontrar una solución mejor.
fuente
Solo si
#include "stack.cpp
al final destack.hpp
. Solo recomendaría este enfoque si la implementación es relativamente grande y si cambia el nombre del archivo .cpp a otra extensión, para diferenciarlo del código normal.fuente
cpp
(cc
o lo que sea) porque eso es un marcado contraste con su función real. En su lugar, debería recibir una extensión diferente que indique que es (A) un encabezado y (B) un encabezado que se incluirá en la parte inferior de otro encabezado. Yo utilizotpp
para esto, que también puede significar fácilmente implementaciónt
emp
latep
(definiciones fuera de línea). Divagué más sobre esto aquí: stackoverflow.com/questions/1724036/…A veces es posible tener la mayor parte de la implementación oculta en el archivo cpp, si puede extraer la funcionalidad común de todos los parámetros de la plantilla en una clase que no sea de plantilla (posiblemente no sea segura). Luego, el encabezado contendrá llamadas de redirección a esa clase. Se utiliza un enfoque similar cuando se lucha con el problema de "hinchazón de la plantilla".
fuente
Si sabe con qué tipos se utilizará su pila, puede instanciarlos explícitamente en el archivo cpp y mantener allí todo el código relevante.
También es posible exportarlos a través de DLL (!), Pero es bastante complicado obtener la sintaxis correcta (combinaciones específicas de MS de __declspec (dllexport) y la palabra clave export).
Lo hemos usado en una biblioteca matemática / geom que tenía una plantilla doble / flotante, pero tenía bastante código. (Busqué en Google en ese momento, aunque hoy no tengo ese código).
fuente
El problema es que una plantilla no genera una clase real, es solo una plantilla que le dice al compilador cómo generar una clase. Necesitas generar una clase concreta.
La forma fácil y natural es poner los métodos en el archivo de encabezado. Pero hay otra manera.
En su archivo .cpp, si tiene una referencia a cada instanciación de plantilla y método que necesita, el compilador las generará allí para usarlas en todo su proyecto.
nuevo stack.cpp:
fuente
Necesita tener todo en el archivo hpp. El problema es que las clases no se crean realmente hasta que el compilador ve que algún OTRO archivo cpp las necesita, por lo que debe tener todo el código disponible para compilar la clase con plantilla en ese momento.
Una cosa que tiendo a hacer es tratar de dividir mis plantillas en una parte genérica sin plantilla (que se puede dividir entre cpp / hpp) y la parte de plantilla específica del tipo que hereda la clase sin plantilla.
fuente
Debido a que las plantillas se compilan cuando es necesario, esto obliga a una restricción para proyectos de varios archivos: la implementación (definición) de una clase o función de plantilla debe estar en el mismo archivo que su declaración. Eso significa que no podemos separar la interfaz en un archivo de encabezado separado y que debemos incluir tanto la interfaz como la implementación en cualquier archivo que use las plantillas.
fuente
Otra posibilidad es hacer algo como:
No me gusta esta sugerencia por cuestión de estilo, pero puede que le convenga.
fuente
cpp
para evitar confusión con los archivos fuente reales . Las sugerencias comunes incluyentpp
ytcc
.La palabra clave 'exportar' es la forma de separar la implementación de la plantilla de la declaración de la plantilla. Esto se introdujo en el estándar C ++ sin una implementación existente. A su debido tiempo, solo un par de compiladores lo implementaron. Lea información detallada en el artículo de Inform IT sobre exportación
fuente
1) Recuerde que la razón principal para separar los archivos .hy .cpp es ocultar la implementación de la clase como un código Obj compilado por separado que se puede vincular al código del usuario que incluye un .h de la clase.
2) Las clases que no son de plantilla tienen todas las variables definidas concreta y específicamente en archivos .hy .cpp. Por lo tanto, el compilador tendrá la información necesaria sobre todos los tipos de datos utilizados en la clase antes de compilar / traducir generar el código de objeto / máquina Las clases de plantilla no tienen información sobre el tipo de datos específico antes de que el usuario de la clase instancia un objeto pasando los datos requeridos tipo:
3) Solo después de esta instanciación, el compilador genera la versión específica de la clase de plantilla para que coincida con los tipos de datos pasados.
4) Por lo tanto, .cpp NO se puede compilar por separado sin conocer el tipo de datos específico del usuario. Por lo tanto, debe permanecer como código fuente dentro de ".h" hasta que el usuario especifique el tipo de datos requerido, luego, se puede generar a un tipo de datos específico y luego se compila
fuente
El lugar donde podría querer hacer esto es cuando crea una combinación de biblioteca y encabezado, y oculta la implementación al usuario. Por lo tanto, el enfoque sugerido es utilizar la instanciación explícita, porque sabe lo que se espera que proporcione su software y puede ocultar las implementaciones.
Aquí hay información útil: https://docs.microsoft.com/en-us/cpp/cpp/explicit-instantiation?view=vs-2019
Para su mismo ejemplo: Stack.hpp
stack.cpp
main.cpp
Salida:
Sin embargo, no me gusta del todo este enfoque, porque permite que la aplicación se dispare en el pie, pasando tipos de datos incorrectos a la clase de plantilla. Por ejemplo, en la función principal, puede pasar otros tipos que se pueden convertir implícitamente a int como s.Push (1.2); y eso es simplemente malo en mi opinión.
fuente
Estoy trabajando con Visual Studio 2010, si desea dividir sus archivos en .hy .cpp, incluya su encabezado cpp al final del archivo .h
fuente