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 <<
:
Funkcja jest zadeklarowana, lecz nie jest od razu zaimplementowana wewnątrz bloku class
. Zamiast tego umieszczamy jej kod osobno:
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
:
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.
a takie cos jak np tutaj http://forum.programuj.com/viewtopic.php?t=10086&highlight=friend+std+ostream+operator ??
Jestem pewien, ze prototyp funkcji specyfikwoany przez friend napewno nie jest deklaracją owej funkcji. Deklaracja, badz definicja z deklaracja muszą gdzies istniec poza klasa.
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 :)
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…
@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.
Tutaj http://smart2help.com/e-books/ticpp-2nd-ed-vol-two/#_Toc53985727 też co nieco na ten temat, warto poczytać.
@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
@Ł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 ;)
“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”. ;)
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.