Posts tagged ‘friend’

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
 


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