2013-03-12 18 views
6

W projektach średniej wielkości lub nawet dużych złożone oddzielanie deklaracji i definicji szablonu jest przydatne w celu skrócenia czasu kompilacji.Co to jest niezawodna metoda specjalizacji szablonów w C++ dla oddzielnego nagłówka/źródła

Jednak w złożonym kodzie błędy małego programisty mogą prowadzić do niezauważonej zmiany zachowania, np. np. zamiast specjalizacji wywoływana jest wersja ogólna.

Przykład: Specjalizacja szablonów stała się niewidoczna z powodu nieodebranego zgłoszenia.

///////////////////// file A.hpp ///////////////////// 

#include <iostream> 

template <typename T> 

class A 
{ 
public: 
    void foo() 
    { 
     std::cerr << " calling generic foo " << std::endl ; 
    } 
}; 

// forgetting following declaration leads to an unintended program behaviour 
template <> void A<int>::foo(); 

///////////////////// file A-foo-int.cpp ///////////////////// 
#include "A.hpp" 

template <> 
void A<int>::foo() 
{ 
    std::cerr << "calling <int> version of foo" << std::endl; 
} 

///////////////////// file main.cpp ///////////////////// 
#include "A.hpp" 

int main(int argc , char** argv) 
{ 
    A<int>* a = new A<int>(); 
    a->foo(); 
    return 0; 
} 

///////////////////// Makefile ///////////////////// 
CC = g++ 
CPPFLAGS += -Wall -O3 
CXXFLAGS += --std=gnu++0x 

all: nonrobust-template-setup 

nonrobust-template-setup: main.o A-foo-int.o 
    $(CC) $(CPPFLAGS) main.o A-foo-int.o -o nonrobust-template-setup 

clean: 
    rm -rf *.o nonrobust-template-setup 

////////////////////////////////////////// 

Pytanie: jest bardziej wytrzymała konfiguracji możliwe (compiler- i niezależny od platformy) i jeśli, jak to będzie wyglądać?

Jeśli nie, jaki jest dobry sposób na sprawdzenie, czy wybrana jest pożądana wersja funkcji?

+0

Myślę, że robisz wszystko, co powinieneś. Tak po prostu działa w C++ :) – cubuspl42

Odpowiedz

2

Nie można oddzielne deklaracje i definicje w ten sposób: jeśli spycha definicję swoich wyspecjalizowanych funkcji składowych w oddzielnym .cpp pliku, bez względu na to, czy zadeklarować swoją specjalizację natychmiast po pierwszym szablonie, kompilator nie będzie mógł stwórz go, a linker będzie narzekał na nierozwiązane referencje.

Normalnie, definicja funkcji członek szablonu klasy idzie w pliku nagłówkowym, chyba podać jednoznaczną konkretyzacji dla odpowiednich szablonów klas:

template class X<int>; // You should add this to make your program build, 
         // or instantiate the corresponding class template 
         // specialization and invoke the foo() method in the 
         // same translation unit (A.cpp) 

W ogóle, chyba że jesteś napotykając na poważne problemy z kompilacją, sugerowałbym, abyś postępował zgodnie z powszechną praktyką i umieścił wszystko w pliku nagłówkowym, który ma być włączony przez wszystkie jednostki tłumaczeniowe, które muszą używać szablonu klasy:

///////////////////// file A.hpp ///////////////////// 

#include <iostream> 

template <typename T> 

class A 
{ 
public: 
    void foo() 
    { 
     std::cerr << "error: called generic foo " << std::endl ; 
    } 
}; 

template <> 
void A<int>::foo() 
{ 
    std::cerr << "calling <int> version of foo" << std::endl; 
} 

///////////////////// file main.cpp ///////////////////// 
#include "A.hpp" 

int main(int argc , char** argv) 
{ 
    A<int>* a = new A<int>(); 
    a->foo(); 
    return 0; 
} 

Jeśli napotkasz naprawdę straszne problemy z kompilacją, możesz oddzielić definicje funkcji składowych i umieścić je w oddzielnych jednostkach tłumaczeniowych z jawnymi instancjami, ale w C++ 11 nie ma czystego/łatwego sposobu na upewnienie się, wszystkie specjalizacje, które usuniesz w oddzielnych plikach .cpp, są zadeklarowane natychmiast po szablonie podstawowym (zgodnie z zaleceniami dobrej praktyki). Gdyby tak było, myślę, że byłby tak popularny, że nie musiałbyś tu przychodzić i pytać o to, ponieważ każdy z nas musi się zmierzyć z takim problemem.

W niektórych przypadkach niektóre fantazyjne makra mogą pomóc, ale z pewnością przyniosłyby więcej korzyści niż bóle konserwacyjne w naprawdę złożonych projektach.

Rozwiązaniem tego problemu została podjęta w standardzie C++ 03 poprzez wprowadzenie słowa kluczowego export, ale doświadczenie realizacja okazała się ona zbyt mocno wspierać sprzedawców kompilatora, dlatego export ma więcej części C++ Standard (od C++ 11).

Mamy nadzieję, że lepsze rozwiązanie dla modułów : spowoduje przejście do C++ 14 i udostępnienie rozwiązania do projektowania szablonów.

+0

OK, wygląda na to, że nie ma dobrego rozwiązania dla tego problemu, z wyjątkiem pisania testów, aby sprawdzić, czy wywoływana jest poprawna wersja metody. –

+0

@JarobKroeker: Tak, właściwie to wrażenie, które próbowałem przekazać. Uważam, że nie ma jeszcze dobrego rozwiązania tego problemu. –

1

Myślę, że najlepsze, co można zrobić, to static_assert, że ogólny szablon nigdy nie jest tworzony z typami, które mają być wyspecjalizowane.

Poniższy kod służy tylko do zilustrowania - prawdopodobnie użyłbym BOOST_STATIC_ASSERT (i std::is_same, gdybym mógł użyć C++ 11). Podstawową ideą jest zapobieganie niejawnemu tworzeniu instancji niespecjalistycznego szablonu z zestawem typów, których nie możesz zabronić. Oczywiście, jeśli zapomnisz dodać statyczny dowództwo ORAZ specjalizację, której wciąż nie uda.

template<class T, class U> 
struct is_same { enum { value = false }; }; 

template<class T> 
struct is_same<T, T> { enum { value = true }; }; 

template <bool enable> 
struct StaticAsserter 
{ 
    char test[enable]; 
}; 

template <typename T> 
struct foo 
{ 
    // Make sure we can't implicit instantiate foo for int. 
    StaticAsserter<!is_same<int, T>::value> DisallowInt; 
}; 

int main() 
{ 
    foo<unsigned> hi; 
    foo<int> fail; 

    return 0; 
} 
+0

Jeśli dobrze zrozumiałem pytanie, OP chce się nie zapomnieć, zaraz po szablonie głównym, aby zadeklarować wyraźną specjalizację, którą później definiuje. W jaki sposób pomoc "static_assert" w tym przypadku? –

+0

ok, na jeden mały projekt prawdopodobnie może wykonać następujące czynności: 'szablon Klasa A { publicznym: void foo() { static_assert (nie std :: is_same :: wartość" int specjalizacja for foo() nie jest instancją! "); } }; ' } ale jest to możliwe do zastosowania w przypadku projektów o średnim rozmiarze. –

0

Sposób, aby mieć pewność, to jest nie dostarcza żadnej definicji generycznego szablonu foo(). Nie ma potrzeby deklarowania specjalizacje gdy robisz to w ten sposób:

// A.h 
template <typename T> struct A { void foo(); }; 
// main.cc 
#include "A.h" 
int main (int c, char **v) 
{ 
    A<int>().foo(); 
    // A<long>().foo(); // this line will compile but not link 
} 
// A.cc 
#include <cstdio> 
#include "A.h" 
template<> void A<int>::foo() { puts("foo!"); } 
+0

W wielu przypadkach potrzebna jest ogólna implementacja i jest przydatna, dlatego nie można jej usunąć. –

+0

Twój dostarczony kod "błąd: nazywany generycznym foo" dla ogólnej implementacji doprowadził mnie do przekonania, że ​​jest inaczej, być może nadal przydatne byłoby wyjaśnienie słów dla innych osób. – jthill

+0

Re: Twój dostarczony kod "error: called generic foo" dla ogólnego Wdrożenie doprowadziło mnie do przekonania, że ​​jest inaczej - tak, to prawda, przepraszam za to. Może powinienem teraz edytować przykład kodu (jeśli to możliwe) –

0

Okay, z uwagi instancji rodzajowego A<T>::foo() realizację niekoniecznie jest błąd, tylko wtedy, gdy dostarczyli specjalizację w innym miejscu.

Chcemy znaleźć instancje generycznych szablonów, których nazwy są duplikatami specjalizacji, które powinny być tworzone tylko na konkretnej liście plików obiektów kompilatora - co ogranicza szukanie pasujących pól w dwóch zestawach danych. Do tego istnieje join:

# every object and where it's defined 
nm -A *.o | c++filt | grep ' T ' \ 
| sort -k3 > @all.definitions 

# definitions that shouldn't be duplicated: 
nm -A A-foo-int.o | c++filt | grep ' T ' \ 
| sort -k3 > @my.definitions 

# everything that shows on both lists: 
join -j3 @my.definitions @all.definitions 

edit: składnia sed za wzorce grep nie naprawdę działa bardzo dobrze.