Szablony extern, biblioteka współdzielona i Windows = podwójne singletony

0

Mój problem dotyczy duplikacji singletonów na Windowsie mimo użycia szablonów extern.

Oto jak to wygląda:

  • Projekt składa się z biblioteki oraz z GUI.
  • Biblioteka tworzy singleton, który opakowuje fabrykę. Zarówno singleton jak i fabryka bazowane są na tych z książki Andrei Alexandrescu (czyli podobne do tych z biblioteki loki).
  • Żeby nie duplikować singletonów, w nagłówkach biblioteki są szablony extern:
extern template class Utils::SimpleSingleton<Utils::SimpleFactory<T1, T2, T3>;
  • Żeby ułatwić sobie życie mam też typedef:
typedef Utils::SimpleSingleton<Utils::SimpleFactory<T1, T2, T3> > CokolwiekFactory;
  • W deklaracjach klas singletonu i fabryki mam te śmieszne Windowsowe dllimport/export. Załatwiam to makrem, które wygenerował mi projekt shared library w Qt.
  • W pliku źródłowym jest instantacja szablonu:
template LIB_EXPORT class Utils::SimpleSingleton<Utils::SimpleFactory<T1, T2, T3> >

W konfiguracji Debug wszystko działa jak należy - singletony są pojedyncze. W konfiguracji Release, która różni się tylko flagą -O2, powstają dwa singletony - jeden wewnątrz biblioteki a drugi podczas pierwszego użycia CokolwiekFactory::getInstance().....

Moje pytanie brzmi: jak to możliwe? extern template zabrania wykonania jakiejkolwiek niejawnej instantacji szablonu. Mimo tego to właśnie ma miejsce podczas działania GUI. Co dziwniejsze dzieje się tak tylko w Release.

Windows 7, kompilator: MinGW 4.6.2. Na Linuksie wszystko działa normalnie.

0

Rozwiązałem ten problem przez usunięcie inline z definicji funkcji szablonowych w fabryce i singletonie. Temat można zamknąć. :-)

0

Mógłbyś podać jakiś minimalny kompilujący się przykład prezentujący problem? Jestem bardzo ciekawy co tam się dzieje.

0

Ok zrobiłem takie coś (singleton_extern_template_test.zip):

  • Library.hpp - plik nagłówkowy biblioteki, extern template i typedef są tutaj.
  • Library.cpp - plik źródłowy biblioteki, instantacja jest tutaj.
  • Singleton.hpp - szablon singletonu.
  • Program.cpp - Program korzystający z biblioteki.

Testuje na Linuksie, gcc 4.6.3. Dodałem Makefile z takimi targetami:

  • lib - buduje bibliotekę.
  • lib_optim - buduje bibliotekę z -O2.
  • app - buduje program.
  • app_optim - buduje program z -O2.
  • all - oba.
  • all_optim - oba z -O2.
  • nm - pokazuje wynik komendy nm.
  • run - uruchamia program.
  • clean - wiadomo.

Bez flagi -O2 oraz z inline w singletonie wynik nm jest taki:

nm --demangle libLibrary.so | grep Singleton
0000000000000700 W Singleton<Library>::instance()
0000000000201028 u Singleton<Library>::instance()::object
nm --demangle Program | grep Singleton
                 U Singleton<Library>::instance()

u oznacza, że symbol jest unikalny, U, że niezdefiniowany. Tak jest dobrze, bo w programie nie ma być definicji, od tego jest biblioteka.

Z flagą -O2 oraz z inline w singletonie wynik nm jest taki:

nm --demangle libLibrary.so | grep Singleton
0000000000000700 W Singleton<Library>::instance()
0000000000201028 u Singleton<Library>::instance()::object
nm --demangle Program | grep Singleton
0000000000602181 u Singleton<Library>::instance()::object

Jak widać symbol ma być unikalny w obu plikach binarnych. Linker to ratuje, bo najpierw ładowana jest biblioteka i jej symbol "wygrywa". Na Windowsie jednak tak to nie działa i są dwa singletony.

Wydaje mi się, że inline nie powinno przeszkadzać - przecież kompilator może to olać. extern template powinno zapewnić brak instantacji w tej jednostce kompilacji.

1

A więc widzę, że stało się to, czego się domyślałem - do implementacji singletonu użyłeś lokalnej zmiennej statycznej - błąd jeśli funkcja może być inline'owana.

Jeszcze mam słabe doświadczenie z szablonami extern, ale mechanizm ten przecież nie uniemożliwia inlne'owania funkcji. extern broni przed powielaniem instancji między modułami. Ale inline'owanie to przecież brak instancji, kod tworzony jest "w miejscu" i nie ma już co linkować.

Dlatego albo robimy funkcję która nie będzie inline, albo zamiast lokalnej zmiennej statycznej używamy statycznego składnika klasy.

0

Zwykłe extern to nie to samo co extern template. Linker może uczynić taką funkcję inline i keyword inline nie powinien przeszkadzać w niczym(źródło). extern template mówi "nie wolno tworzyć konkretnej wersji tego szablonu, gdzieś indziej już taka jest" więc ta funkcja, inline czy nie, nie może w ogóle powstać. Dlatego zmienna static w funkcji nie powinna istnieć, bo ta funkcja nie powinna istnieć.

Zwróć uwagę, że problem jest tylko przy -O2. Widocznie mimo tego, że szablon ma być extern template kompilator uznaje inline i robi tę funkcje inline przez co powstaje ta statyczna zmienna i wszystko się psuje. (Tak jak napisałeś, w miejscu powstaje ta statyczna zmienna) Dlaczego jednak kompilator nie patrzy najpierw na to, że szablon jest extern template? Przecież standard mówi, że inline można olać, jeżeli nie można go wykonać.

Poza tym AFAIK zmienna static w inline to nie jest błąd. To zadanie kompilatora/linkera, żeby zagwarantować, że ta zmienna będzie unikalna. Jak zresztą widać na Linuksie tak się właśnie dzieje - Windowsowa wersja coś robi źle. Edit: sprawdziłem w standardzie. Punkt 7.1.2.4: "A static local variable in an extern inline function always refers to the same object." Więc to powinno działać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1