2017-01-11 77 views
7

Zauważyłem dość dziwne zachowanie inicjalizacji zmiennej statycznej w szablonach funkcji. Rozważmy następujący przykład:Inicjalizacja zmiennych statycznych C++ wewnątrz funkcji szablonu

MyFile * createFile() 
{ 
    std::cout << "createFile" << std::endl; 
    return nullptr; 
} 

template <typename T> 
void test(const T& t) 
//void test(T t) 
{ 
    static MyFile *f = createFile(); 
} 

void main() 
{ 
    test("one"); 
    //test("two"); 
    test("three"); 
} 

Dopóki f w test jest statyczna, spodziewałem createFile być wywołana tylko raz. Jednak nazywa się to dwukrotnie.

Spędziwszy trochę czasu nad problemami, zauważyłem, że usunięcie odwołania do stałych z argumentu w test rozwiązuje problem. Kolejną interesującą rzeczą jest to, że długość łańcucha przekazywana do funkcji wpływa również na inicjalizację: gdy długość parametrów jest równa, zmienna statyczna jest inicjowana tylko raz, w przeciwnym razie następuje inicjalizacja.

Czy ktoś mógłby to wyjaśnić? Rozwiązania/obejścia poza wspomnianymi są bardzo mile widziane.

+6

Tworzysz szablon z 2 różnymi 'T's,' char [4] '(' "one" ') i' char [6] '(' "three" '). Dlatego masz dwa nowe typy, oba zawierające 'f', dlatego oba te' f's muszą zostać zainicjalizowane. Stąd 'createFile()' jest wywoływane dwa razy. – BoBTFish

+1

OK, ale dlaczego działa prosta wersja szablonu (bez stałej wartości stałej)? – mentalmushroom

+1

Zanik wskaźnika (nie można pobrać tablicy według wartości). Co nie jest wystarczająco proste, aby opisać w komentarzu. Pozwolę komuś innemu opisać to w odpowiedzi. – BoBTFish

Odpowiedz

6

Dosłowny "jeden" to const char [4].

ten kod:

test("one") 

byłoby idealnie lubią nazywać test(const char (&)[4])

Działa to dla test(const T&) (bo const char (&) [4] może wiązać się const char (const&) [4]).

Ale nie może działać dla test(T t), ponieważ nie można przekazywać literałów łańcuchowych według wartości. Są one przekazywane przez odniesienie.

Jednak const char[4] może zepsuć się do const char*, które można dopasować template<class T> void func(T t).

Dowód jest w puddingu:

#include <cstdint> 
#include <iostream> 
#include <typeinfo> 

template <typename T, std::size_t N> 
void test_const(const T(&t)[N]) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << " and N is " << N << std::endl; 
} 

template <typename T> 
void test_mutable(T &t) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl; 
} 

template <typename T> 
void test_const_ref(const T &t) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl; 
} 

template <typename T> 
void test_copy(T t) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl; 
} 

int main() 
{ 
    test_const("one"); 
    test_const("three"); 
    test_mutable("one"); 
    test_mutable("three"); 
    test_const_ref("one"); 
    test_const_ref("three"); 
    test_copy("one"); 
    test_copy("three"); 
} 

przykładowe wyniki (szczęk):

test_const for literal one T is a c and N is 4 
test_const for literal three T is a c and N is 6 
test_mutable for literal one T is a A4_c 
test_mutable for literal three T is a A6_c 
test_const_ref for literal one T is a A4_c 
test_const_ref for literal three T is a A6_c 
test_copy for literal one T is a PKc 
test_copy for literal three T is a PKc 

Oto wersja z nazwami demangled (będzie kompilować na brzękiem i gcc):

#include <cstdint> 
#include <iostream> 
#include <typeinfo> 
#include <cstdlib> 
#include <cxxabi.h> 

std::string demangle(const char* name) 
{ 
    int status = -1; 
    // enable c++11 by passing the flag -std=c++11 to g++ 
    std::unique_ptr<char, void(*)(void*)> res { 
     abi::__cxa_demangle(name, NULL, NULL, &status), 
     std::free 
    }; 

    return (status==0) ? res.get() : name ; 
} 

template <typename T, std::size_t N> 
void test_const(const T(&t)[N]) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << " and N is " << N << std::endl; 
} 

template <typename T> 
void test_mutable(T &t) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl; 
} 

template <typename T> 
void test_const_ref(const T &t) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl; 
} 

template <typename T> 
void test_copy(T t) 
{ 
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl; 
} 

int main() 
{ 
    test_const("one"); 
    test_const("three"); 
    test_mutable("one"); 
    test_mutable("three"); 
    test_const_ref("one"); 
    test_const_ref("three"); 
    test_copy("one"); 
    test_copy("three"); 
} 

oczekiwana moc wyjściowa:

test_const for literal one T is a char and N is 4 
test_const for literal three T is a char and N is 6 
test_mutable for literal one T is a char [4] 
test_mutable for literal three T is a char [6] 
test_const_ref for literal one T is a char [4] 
test_const_ref for literal three T is a char [6] 
test_copy for literal one T is a char const* 
test_copy for literal three T is a char const* 
+0

@Quentin to nie może tego wydedukować. To sprawi, że argument "const const char" i " –

+0

Woops, pozwól mi spróbować ponownie. To deduces 'const char [4]' dla 'T' :) – Quentin

+0

@Quentin wiodąca const to uniemożliwia. Byłby to "const const char [4]", który nie jest legalny. –

0

Jako uzupełnienie użytkownika @ RichardHodges odpowiedź, która wyjaśnia, dlaczego stosowane są różne instanciations, nie jest łatwo zmusić tylko jeden, ponieważ tablice można rozkładać do wskaźnika z wyraźną szablonu instanciation:

test<const char *>("one"); 
test<const char *>("two"); 
test<const char *>("three"); 

skutkować jednym jedna rozmowa z numerem createFile.

W rzeczywistości (jak powiedział w komentarzu przez BoBTFish), to jest dokładnie to, co się dzieje, gdy piszesz:

template <typename T> 
void test(const T t) 

Niezależnie od wielkości tablicy, tablica automatycznie zanika do const char * ponieważ C++ nie zezwala aby przypisać bezpośrednio tablice.

BTW, void main() jest bad. Zawsze używaj int main() i wyraźnego zwrotu.