2013-05-06 11 views
7

Mam funkcje szablonów w moim projekcie Xcode C++ 11, a niektóre z nich mają specjalizacje. Jednakże odkryłem, że specjalizacje są wywoływane tylko w kompilacjach debugowania; jeśli zbuduję wersję, są one ignorowane.Dlaczego moja wyspecjalizowana funkcja szablonu wywoływana jest tylko w kompilacjach debugowania?

Mam powodzeniem stworzył bardzo prosty przykład:

special.h

#include <cstdio> 

struct special 
{ 
    template<typename T> 
    void call(const T&) { puts("not so special"); } 
}; 

special.cpp

#include "special.h" 
#include <string> 

template<> 
void special::call(const std::string&) { puts("very special"); } 

main.cpp

#include "special.h" 
#include <string> 

int main() 
{ 
    std::string str = "hello world"; 
    special s; 
    s.call(123); 
    s.call(str); 
} 

You can download the project (przynajmniej latem 2013 r.), Aby odtworzyć problem, jeśli nie chcesz go tworzyć samodzielnie. Najpierw uruchom projekt z konfiguracją debugowania, a następnie uruchom go ponownie w wydaniu. Wyjście że spodziewam się:

nie jest tak wyjątkowy
wyjątkowy

I to jest rzeczywiście to, co się z konfiguracją Debug build. Jednak od wersji, otrzymuję to:

nie tak specjalnego
nie tak wyjątkowe

co oznacza specjalistyczną realizację special::call w special.cpp został zignorowany.

Dlaczego wynik jest niespójny? Co powinienem zrobić, aby funkcja wyspecjalizowana została wywołana w wersjach wydania?

+0

Umieścić w .h zamiast .cpp? – Pubby

+0

special.cpp jest połączone w obu przypadkach? – ForEveR

+0

Tak, plik special.cpp jest kompilowany i łączony w obu przypadkach. Jeśli umieścisz specjalizację w pliku nagłówkowym, działa, jeśli plik jest zawarty tylko w jednym pliku .cpp, ale w przeciwnym razie dostaję duplikat błędu symbolu. – zneak

Odpowiedz

11

Twój program ma UB. Wyraźna specjalizacja lub przynajmniej jej deklaracja musi być widoczna przed użyciem. [Temp.expl.spec] §6:

Jeśli szablon, szablon członek lub członek szablonu klasy jest wyraźnie wyspecjalizowane wtedy specjalizacji zgłasza przed pierwszym użyciem tej specjalności, które powoduje niejawne tworzenie wystąpień, w każdej jednostce tłumaczeniowej w danym przypadku; diagnostyka nie jest wymagana.

Dodaj tę deklarację :

template<> 
void special::call(const std::string&); 

Alternatywnie, można umieścić samą specialistation w nagłówku. Ponieważ jednak specjalizacja nie jest już szablonem, jest zgodna z normalnymi regułami funkcji i musi być oznaczona jako inline, jeśli zostanie umieszczona w nagłówku.

Należy również pamiętać, że specjalizacje szablonów funkcji mają raczej specyficzne zachowanie i ogólnie lepiej jest stosować przeciążenia niż specjalizacje. Aby uzyskać szczegółowe informacje, patrz Herb Sutter's article.

8

Naruszono regułę jednej definicji (ODR). Co więc dzieje się dokładnie? W main.cpp nie ma specjalizacji znanej z special::call<string>. Dlatego też kompilator generuje instancję szablonu w tej jednostce tłumaczeniowej (TU), która wyświetla "nie jest tak wyjątkowa". W special.cpp jest zadeklarowana i zdefiniowana pełna specjalizacja, więc kompilator umieszcza tę definicję w innej jednostce tłumaczeniowej. Masz dwie różne definicje tej samej funkcji w dwóch różnych jednostkach tłumaczeniowych, co stanowi naruszenie ODR, co oznacza, że ​​jest niezdefiniowanym zachowaniem.

Teoretycznie wynikiem może być wszystko. Błąd kompilatora, awaria, ciche zamówienie online na pizzę, dowolna informacja:. Nawet inne zachowanie w kompilacjach debugowania i wydania.

W praktyce I odgadnąć następujące czynności: Podczas łączenia kompilacji debugowania, Linker widzi ten sam symbol zdefiniowany dwa razy w dwóch JT, co jest dozwolone tylko dla szablonów i funkcji inline. Ze względu na ODR można założyć, że obie definicje są równoważne i wybiera je z special.cpp, więc otrzymujesz przypadkowo oczekiwane zachowanie.
Podczas kompilacji wydania kompilator inicjuje wywołanie special::call<string> podczas kompilacji main.cpp, więc uzyskujesz jedyne zachowanie widoczne w tej JT: "nie jest tak wyjątkowe".

Jak możesz to naprawić?
Aby uzyskać tylko jedną definicję dla tej specjalizacji, musiszzdefiniowaćw jednym TU, ale musisz zadeklarować, że istnieje pełna specjalizacja w dowolnej innej JT, co oznacza, że ​​deklaracja specjalizacji istnieje w nagłówku :

// in special.h 
template<> 
void special::call(const std::string&); 

Albo, co jest postrzegane częściej określić to w nagłówku, więc jest to widoczne w każdym TU. Ponieważ w pełni wyspecjalizowane szablony funkcji są normalnymi funkcjami, będziesz musiał zdefiniować je w linii:

// in special.h 
template<> 
inline void special::call(const std::string&) 
{ 
    puts("very special"); 
} 
+0

+1, aby uzyskać pełne wyjaśnienie, co się dzieje, a nie tylko wymaganą korektę. Jeśli chodzi o wybór funkcji z 'specials.cpp', przypuszczam, że wynika to z kolejności, w której pliki' .o' są przekazywane do linkera; w szczególności podejrzewam, że 'specials.o' jest przekazywane przed' main.o'. –

+0

Sądzę, że jest to * bardzo * specyficzna część implementacji. Będzie to zależało od kolejności, w jakiej linker * przetwarza * pliki obiektów, co z kolei zależy od kolejności, w jakiej pliki obiektów są przekazywane do niej za pomocą wiersza poleceń. I może zależeć od tego, jak linker przechowuje i wyszukuje symbole znajdujące się w dwóch JT. To może być wszystko, ale domyślam się, że najbardziej oczywistym byłoby, że pierwsze pliki obiektów przekazane są pierwszym przetworzonym, a pierwszy raz znaleziony symbol jest jedynym, który zostaje zapisany i wywołany. –

+0

Masz rację, wydaje się, że jest to ściśle związane z konkretną implementacją, ale ponieważ najwyraźniej zachowanie jest * stabilne *, prawdopodobnie istnieje wyjaśnienie. –

Powiązane problemy