definición múltiple de especialización de plantillas cuando se utilizan diferentes objetos

95

Cuando utilizo una plantilla especializada en diferentes archivos de objeto, obtengo un error de "definición múltiple" al vincular. La única solución que encontré implica el uso de la función "en línea", pero parece una solución alternativa. ¿Cómo soluciono eso sin usar la palabra clave "en línea"? Si eso no es posible, ¿por qué?

Aquí está el código de ejemplo:

paulo@aeris:~/teste/cpp/redef$ cat hello.h 
#ifndef TEMPLATE_H
#define TEMPLATE_H

#include <iostream>

template <class T>
class Hello
{
public:
    void print_hello(T var);
};

template <class T>
void Hello<T>::print_hello(T var)
{
    std::cout << "Hello generic function " << var << "\n";
}

template <> //inline
void Hello<int>::print_hello(int var)
{
    std::cout << "Hello specialized function " << var << "\n";
}

#endif

paulo@aeris:~/teste/cpp/redef$ cat other.h 
#include <iostream>

void other_func();

paulo@aeris:~/teste/cpp/redef$ cat other.c 
#include "other.h"

#include "hello.h"

void other_func()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);
}

paulo@aeris:~/teste/cpp/redef$ cat main.c 
#include "hello.h"

#include "other.h"

int main()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);

    other_func();

    return 0;
}

paulo@aeris:~/teste/cpp/redef$ cat Makefile
all:
    g++ -c other.c -o other.o -Wall -Wextra
    g++ main.c other.o -o main -Wall -Wextra

Finalmente:

paulo@aeris:~/teste/cpp/redef$ make
g++ -c other.c -o other.o -Wall -Wextra
g++ main.c other.o -o main -Wall -Wextra
other.o: In function `Hello<int>::print_hello(int)':
other.c:(.text+0x0): multiple definition of `Hello<int>::print_hello(int)'
/tmp/cc0dZS9l.o:main.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status
make: ** [all] Erro 1

Si elimino el comentario "en línea" dentro de hello.h, el código se compilará y se ejecutará, pero eso me parece una especie de "solución": ¿qué pasa si la función especializada es grande y se usa muchas veces? ¿Obtendré un binario grande? Hay alguna otra manera de hacer esto? Si es así, ¿cómo? Si no, ¿por qué?

Traté de buscar respuestas, pero todo lo que obtuve fue "usar en línea" sin más explicaciones.

Gracias

pzanoni
fuente
6
poner la implementación especializada real en .cpp en lugar del archivo de encabezado
Anycorn

Respuestas:

129

Intuitivamente, cuando se especializa completamente en algo, ya no depende de un parámetro de plantilla, por lo que, a menos que realice la especialización en línea, debe colocarla en un archivo .cpp en lugar de .h o terminará violando la una regla de definición como dice David. Tenga en cuenta que cuando se especializa parcialmente en las plantillas, las especializaciones parciales aún dependen de uno o más parámetros de la plantilla, por lo que todavía van en un archivo .h.

Stuart Golodetz
fuente
Hmmm, todavía estoy un poco confundido acerca de cómo se rompe el ODR. Porque solo define la plantilla completamente especializada una vez. Es posible que esté creando el objeto varias veces en diferentes archivos de objeto (es decir, en este caso se crea una instancia en other.cy main.c) pero el objeto original en sí se define solo en un archivo, en este caso hello.h.
Justin Liang
3
@JustinLiang: El encabezado se incluye en dos archivos .c separados, que tiene el mismo efecto que si hubiera escrito su contenido (incluida la especialización completa) directamente en los archivos en los que está incluido en los lugares relevantes. La regla de una definición (ver en.wikipedia.org/wiki/One_Definition_Rule ) dice (entre otras cosas): "En todo el programa, un objeto o función no en línea no puede tener más de una definición". En este caso, la especialización completa de la plantilla de función es, en esencia, como una función normal, por lo que, a menos que esté en línea, no puede tener más de una definición.
Stuart Golodetz
Hmmm, me di cuenta de que cuando no tenemos una especialización basada en plantillas, este error no aparece. Digamos que teníamos dos funciones diferentes que se definieron en el archivo de encabezado, fuera de la clase, ¿seguirán funcionando sin el inline? Por ejemplo: pastebin.com/raw.php?i=bRaiNC7M . Tomé esa clase y la incluí en dos archivos. ¿No tendría esto "el mismo efecto que si hubiera escrito el contenido" directamente en los dos archivos y, por lo tanto, habrá un error de definición múltiple?
Justin Liang
@Justin Liang, su código de encabezado basado en la clase seguirá violando el ODR si se incluye en varios archivos, a menos que las definiciones de función estén dentro del cuerpo de la clase.
haripkannan
Entonces, si mi definición de miembro estático está precedida por, template <typename T>entonces puede ir a un encabezado, y si es template<>así, ¿puede que no?
Violet Giraffe
49

La palabra clave inlinetiene más que ver con decirle al compilador que el símbolo estará presente en más de un archivo de objeto sin violar la regla de una definición que con la inserción real, que el compilador puede decidir hacer o no hacer.

El problema que está viendo es que sin el inline, la función se compilará en todas las unidades de traducción que incluyen el encabezado, violando el ODR. Agregar inlinees el camino correcto a seguir. De lo contrario, puede reenviar declarar la especialización y proporcionarla en una sola unidad de traducción, como lo haría con cualquier otra función.

David Rodríguez - dribeas
fuente
22

Ha creado una instancia explícitamente de una plantilla en su encabezado ( void Hello<T>::print_hello(T var)). Esto creará múltiples definiciones. Puedes solucionarlo de dos formas:

1) Haga su instanciación en línea.

2) Declare la instanciación en un encabezado y luego impleméntela en un cpp.

Edward extraño
fuente
En realidad, hay una tercera forma que es ponerlos en un espacio de nombres sin nombre ... que es similar a tener estática en C.
Alexis Wilke
4
Eso no es válido aquí. Una especialización de plantilla debe estar en el mismo espacio de nombres que la plantilla original.
Edward Strange
0

Aquí hay una parte del estándar C ++ 11 relacionada con este problema:

Una especialización explícita de una plantilla de función está en línea solo si se declara con el especificador en línea o se define como eliminada, e independientemente de si su plantilla de función está en línea. [Ejemplo:

plantilla void f (T) {/ * ... /} plantilla en línea T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // OK: plantilla en línea <> int g <> (int) {/ ... * /} // OK: no en línea - fin ejemplo]

Por lo tanto, si realiza algunas especializaciones explícitas (también conocidas como completas) de plantillas en el *.harchivo, aún inlinedeberá ayudarlo a deshacerse de la violación de ODR .

Francis
fuente