beberapa definisi spesialisasi template saat menggunakan objek yang berbeda

96

Ketika saya menggunakan template khusus di file objek yang berbeda, saya mendapatkan kesalahan "definisi ganda" saat menghubungkan. Satu-satunya solusi yang saya temukan melibatkan penggunaan fungsi "inline", tetapi sepertinya ada beberapa solusi. Bagaimana cara mengatasinya tanpa menggunakan kata kunci "sebaris"? Jika itu tidak memungkinkan, mengapa?

Berikut adalah contoh kode:

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

Akhirnya:

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

Jika saya menghapus komentar "inline" di dalam hello.h, kode akan dikompilasi dan dijalankan, tetapi itu sepertinya semacam "solusi" bagi saya: bagaimana jika fungsi khusus besar dan digunakan berkali-kali? Apakah saya akan mendapatkan biner besar? Apakah ada cara lain untuk melakukan hal ini?. Jika ya, bagaimana caranya? Jika tidak, mengapa?

Saya mencoba mencari jawaban, tetapi yang saya dapatkan hanyalah "gunakan sebaris" tanpa penjelasan lebih lanjut.

Terima kasih

pzanoni
sumber
7
menempatkan implementasi khusus yang sebenarnya ke dalam .cpp daripada file header
Anycorn

Jawaban:

130

Secara intuitif, ketika Anda sepenuhnya mengkhususkan sesuatu, itu tidak bergantung pada parameter template lagi - jadi kecuali Anda membuat spesialisasi sebaris, Anda perlu meletakkannya dalam file .cpp bukan .h atau Anda akhirnya melanggar satu aturan definisi seperti yang dikatakan David. Perhatikan bahwa ketika Anda mengkhususkan sebagian template, spesialisasi parsial masih bergantung pada satu atau beberapa parameter template, jadi mereka masih masuk dalam file .h.

Stuart Golodetz
sumber
Hmmm saya masih agak bingung bagaimana cara merusak ODR. Karena Anda hanya menentukan template yang sepenuhnya terspesialisasi satu kali. Anda mungkin membuat objek beberapa kali dalam file objek yang berbeda (mis. Dalam kasus ini dibuat di other.c dan main.c) tetapi objek asli itu sendiri hanya didefinisikan dalam satu file - dalam kasus ini hello.h.
Justin Liang
3
@JustinLiang: Header disertakan dalam dua file .c terpisah - yang memiliki efek yang sama seperti jika Anda menulis kontennya (termasuk spesialisasi lengkap) langsung ke dalam file yang disertakan di tempat yang relevan. Aturan Satu Definisi (lihat en.wikipedia.org/wiki/One_Definition_Rule ) mengatakan (antara lain): "Di seluruh program, sebuah objek atau fungsi non-sebaris tidak dapat memiliki lebih dari satu definisi". Dalam hal ini, spesialisasi penuh dari template fungsi pada dasarnya sama seperti fungsi normal, jadi kecuali itu sebaris, ia tidak dapat memiliki lebih dari satu definisi.
Stuart Golodetz
Hmmm, saya perhatikan bahwa ketika kami tidak memiliki spesialisasi kerangka, kesalahan ini tidak akan muncul. Katakanlah kita memiliki dua fungsi berbeda yang ditentukan di file header, di luar kelas, mereka masih akan bekerja tanpa inline? Misalnya: pastebin.com/raw.php?i=bRaiNC7M . Saya mengambil kelas itu dan memasukkannya ke dalam dua file. Bukankah ini memiliki "efek yang sama seperti jika Anda menulis isinya" secara langsung ke dalam dua file dan dengan demikian akan ada kesalahan definisi ganda?
Justin Liang
@ Justin Liang, kode header berbasis kelas Anda masih akan melanggar ODR jika disertakan dalam banyak file, kecuali jika definisi fungsi berada di dalam badan kelas.
haripkannan
Jadi, jika definisi anggota statis saya diawali dengan template <typename T> maka itu mungkin masuk ke header, dan jika template<>mungkin tidak?
Violet Giraffe
49

Kata kunci inlinelebih tentang memberi tahu kompiler bahwa simbol akan ada di lebih dari satu file objek tanpa melanggar Aturan Satu Definisi daripada tentang sebaris sebenarnya, yang dapat diputuskan oleh kompilator untuk dilakukan atau tidak.

Masalah yang Anda lihat adalah tanpa inline, fungsi tersebut akan dikompilasi di semua unit terjemahan yang menyertakan header, melanggar ODR. Menambahkan di inlinesana adalah cara yang tepat untuk pergi. Jika tidak, Anda dapat meneruskan mendeklarasikan spesialisasi dan menyediakannya dalam satu unit terjemahan, seperti yang akan Anda lakukan dengan fungsi lainnya.

David Rodríguez - dribeas
sumber
22

Anda secara eksplisit membuat contoh template di header Anda (void Hello<T>::print_hello(T var) ) Anda. Ini akan menciptakan banyak definisi. Anda dapat menyelesaikannya dengan dua cara:

1) Buat instantiasi Anda sebaris.

2) Deklarasikan instantiation di header dan kemudian implementasikan di cpp.

Edward Strange
sumber
Sebenarnya ada cara ke-3 yaitu meletakkannya di namespace tanpa nama ... yang mirip dengan memiliki statis di C.
Alexis Wilke
4
Itu tidak berlaku di sini. Spesialisasi template harus berada dalam namespace yang sama dengan template asli.
Edward Strange
0

Berikut adalah beberapa bagian dari standar C ++ 11 yang terkait dengan masalah ini:

Spesialisasi eksplisit dari template fungsi adalah sebaris hanya jika ia dideklarasikan dengan penentu sebaris atau didefinisikan sebagai dihapus, dan terlepas dari apakah templat fungsinya sebaris. [Contoh:

template void f (T) {/ * ... /} template inline T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // OK: template inline <> int g <> (int) {/ ... * /} // OK: bukan inline - end contoh]

Jadi jika Anda membuat beberapa spesialisasi eksplisit (alias penuh) template dalam *.hfile, maka Anda masih perlu inlinemembantu Anda menyingkirkan pelanggaran ODR .

Francis
sumber