Szablony klas i funkcje zaprzyjaźnione

2010-06-04 21:18

Chcę dzisiaj napisać o pewnego rodzaju “ciekawostce”, związanej z dwoma pojęciami z tytułu notki, na którą natknąłem się jakiś czas temu. Po zredukowaniu nadmiaru szczegółów można przyjąć, że rzecz dotyczy sytuacji, w której mamy szablon klasy oraz zadeklarowaną wewnątrz niego funkcję zaprzyjaźnioną – na przykład przeciążony operator strumieniowy <<:

  1. #include <iostream>
  2.  
  3. template <typename T> class Foo
  4. {
  5.     private: T x;
  6.     public:
  7.         explicit Foo(T _x) : x(_x) { }
  8.         friend std::ostream& operator << (std::ostream& out, const Foo<T>& foo);
  9. };

Funkcja jest zadeklarowana, lecz nie jest od razu zaimplementowana wewnątrz bloku class. Zamiast tego umieszczamy jej kod osobno:

  1. template <typename T> std::ostream& operator << (std::ostream& out, const Foo<T>& foo)
  2.     { return out << foo.x; }&#91;/cpp]
  3. co jest może trochę osobliwe, ale wydaje się poprawne. No właśnie; wydaje się. Próba użycia tak zdefiniowana operatora:
  4. &#91;cpp]Foo<int> foo(42);
  5. std::cout << foo << std::endl; // bum[/cpp]
  6. nie przeszkadza wprawdzie kompilatorowi, jednak linker ma poważne zastrzeżenia. Okazuje się, że funkcja <code>operator &lt;&lt;</code> w wymaganej postaci nie jest zdefiniowana - czyli mamy zwyczajny <em>unresolved external</em>, aczkolwiek o niecodziennych przyczynach.
  7.  
  8. Przyznać muszę teraz, że nie jestem do końca pewien co do ich natury, jako że konsultacja z zaufanymi źródłami (w postaci książki <a href="http://helion.pl/ksiazki/c_szablony_vademecum_profesjonalisty_david_vandevoorde_nicolai_m_josuttis,cpszav.htm"><em>C++. Szablony</em></a> panów Vandevoorde'a i Josuttisa) nie dała jednoznacznej odpowiedzi, co tutaj tak naprawdę się dzieje. Przypuszczam, że ma tu miejsce pewien dziwnej interakcji między konkretyzacją szablonu klasy a deklaracją <code>friend</code>, która przy okazji jest też deklaracją funkcji <code>operator &lt;&lt;</code>. Mimo że nie stanowi ona części szablonu, owej konkretyzacji <strong>podlega</strong>, czego skutkiem jest stworzenie <strong>zwykłej</strong> (nieszablonowej) deklaracji:
  9. [cpp]std::ostream& operator << (std::ostream& out, const Foo<int>& foo);

Nie ma więc ona nic wspólnego z szablonem funkcji operator <<, który jest zdefiniowany później! W rzeczywistości wygląda zresztą tak, że ów szablon jest całkowicie pominięty przy kompilacji jako niewykorzystywany; to zapewne z powodu reguł przeciążania funkcji w C++, nakazujących najwyraźniej wybranie raczej funkcji nieszablonowej (o powyższej deklaracji) zamiast szablonu funkcji. A że przypadkiem funkcja ta nie ma implementacji… Cóż, kompilator nie ma obowiązku się o to martwić :)

Tyle moja teoria. Niezależnie od tego, czy jest ona prawdziwa czy nie, dobrze byłoby wiedzieć, jak z problemu wybrnąć. Naturalnym wyjściem jest przenieść kod funkcji tam, gdzie jego miejsce – czyli z deklaracji funkcji w klasie (opatrzonej słówkiem friend) uczynić też jej definicję. Faktycznie, to działa; najwyraźniej powstaje wtedy nieszablonowa funkcja o nagłówku danym wyżej, razem ze swoją implementacją – czyli wszystko jest w porządku.
Drugie rozwiązanie to opatrzenie deklaracji friend klauzulą template:

  1. template <typename T>
  2.     friend std::ostream& operator << (std::ostream& out, const Foo<T>& foo);

W tej wersji – jak sądzę – następuje zaprzyjaźnienie każdej specjalizacji klasy Foo z odpowiadającą jej specjalizacją metody operator <<. Oba szablony podlegają niezależnym konkretyzacjom, ale linker potrafi prawidłowo rozwiązać relację między nimi.

Uff, trochę to skomplikowane. Jak widać, z zaprzyjaźnianiem bywają problemy – i to nie tylko dlatego, iż “In C++ friends touch each other’s private parts.” ;) Dziwne efekty związane z szablonami i konkretyzacją potrafią bowiem skonfudować nawet doświadczonych programistów.

Tags: , , ,
Author: Xion, posted under Programming »


10 comments for post “Szablony klas i funkcje zaprzyjaźnione”.
  1. dreugh:
    June 4th, 2010 o 23:00

    a takie cos jak np tutaj http://forum.programuj.com/viewtopic.php?t=10086&highlight=friend+std+ostream+operator ??

  2. Malcom:
    June 5th, 2010 o 0:23

    Jestem pewien, ze prototyp funkcji specyfikwoany przez friend napewno nie jest deklaracją owej funkcji. Deklaracja, badz definicja z deklaracja muszą gdzies istniec poza klasa.

  3. dikamilo:
    June 5th, 2010 o 9:57

    Ostatnio właśnie miałem taki problem, wyjściem było albo definicja funkcji zaprzyjaźnionej bezpośrednio w klasie, albo stworzenie deklaracji szablowych i klasy i funkcji zaprzyjaźnionej na samym początku i wtedy też działa :)

  4. dreugh:
    June 5th, 2010 o 15:51

    przetestowałam to troszke. I nie bardzo rozumie czemu ma służyć znaczek <> z którego tam skorzystali w linku wyzej. Skasowałam ten znaczek przy deklaracji operatora + i kompilator sie sprzeciwił, więc teraz to już kompletnie nie rozumie bo sama też miałam ten problem kiedyś i zamiast operatora + zaczęłam stosować funkcje “suma” :D. Dlaczego z operatorami tak jest a ze zwykłymi funkcjami już nie…

  5. Xion:
    June 5th, 2010 o 18:16

    @Malcom: Niestety mylisz się – deklaracja przyjaźni może być deklaracją samej funkcji, jak również jej definicją. Najprostszy przykład:
    class Foo
    {
    public: int X;
    friend std::ostream& operator << (std::ostream& out, const Foo& f) { return out << X; } };[/cpp] Deklaracja przyjaźni jest tu i zwykła deklaracją funkcji, jak również definicją. Wariant z deklaracja funkcji opatrzoną słówkiem friend (czyli będącą jednocześnie deklaracją przyjaźni), ale z definicją/implementacją poza klasą: [cpp]class Foo { public: int X; friend std::ostream& operator << (std::ostream& out, const Foo& f); }; std::ostream& operator << (std::ostream& out, const Foo& f) { return out << X; }[/cpp] jest również najzupełniej poprawny.

  6. Łukasz Gąsiorek:
    June 5th, 2010 o 21:58

    Tutaj http://smart2help.com/e-books/ticpp-2nd-ed-vol-two/#_Toc53985727 też co nieco na ten temat, warto poczytać.

  7. Malcom:
    June 6th, 2010 o 12:45

    @Xion: Dzialac to dziala, ale chcialem sie dowiedziec jak to wyglada ze strony czysto formalnej. Ale zerknalem do stdandardu i juz sie sprawa rozwiazala, przy okazji dowiadujac sie roznych ciekawych rzeczy zwiazanych z firend i jego mozliwosciami.

    Jak chociazby rodzaj wiazania przy deklaracjach z friendem, definicja z friendem moze znajdowac sie tylko w nie-lokalnych klasach, a sama funkcja bedzie “lezec” w tej samej przestrzeni nazw co definicja klasy i jest implicitly inline ;)

    Wypada tylko podziekowac za inspiracje i “zmuszenie” do zerkniecia w dokumenty ;p

  8. Xion:
    June 6th, 2010 o 14:34

    @Łukasz Gąsiorek: Dzięki za tego linka! Nie wiedziałem, że w TinC++ jest to tak ładnie opisane. Może jednak czasami ta pozycja się do czegoś przydaje ;)

  9. Łukasz Gąsiorek:
    June 7th, 2010 o 19:03

    “Może jednak czasami ta pozycja się do czegoś przydaje ;)”
    Słucham? Przecież ta pozycja (także tom 1) jest obowiązkowa! A nie “czasem się przydaje”. ;)

  10. Xion:
    June 7th, 2010 o 22:13

    Rzeczywiście, ładnie wygląda na półce (aczkolwiek ta od Javy jeszcze lepiej). Ale mnie znudziła po połowie pierwszego rozdziału. Niby ona dla początkujących, ale im poleciłbym już bardziej Symfonię. A dla zaawansowanych są bardziej wyspecjalizowane pozycje – jak choćby ta, którą wspominam w notce.

Comments are disabled.
 


© 2019 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.