W pięknej odpowiedzi na idiom `` kopiuj i zamień '' jest fragment kodu, potrzebuję trochę pomocy:
class dumb_array
{
public:
// ...
friend void swap(dumb_array& first, dumb_array& second) // nothrow
{
using std::swap;
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
// ...
};
i dodaje notatkę
Istnieją inne twierdzenia, że powinniśmy specjalizować się std :: swap dla naszego typu, zapewnić wymianę w klasie obok wymiany wolnej funkcji itp. Ale to wszystko jest niepotrzebne: każde właściwe użycie swap będzie poprzez niekwalifikowane wywołanie , a nasza funkcja zostanie znaleziona przez ADL. Jedna funkcja wystarczy.
Ze friend
jestem trochę na „nieprzyjaznych” warunki, muszę przyznać. Tak więc moje główne pytania to:
- wygląda jak funkcja wolna , ale znajduje się w treści klasy?
- dlaczego to nie jest
swap
statyczne ? Oczywiście nie używa żadnych zmiennych składowych. - "Jakiekolwiek właściwe użycie zamiany spowoduje, że zamiana przez ADL" ? ADL przeszuka przestrzenie nazw, prawda? Ale czy wygląda to również w klasach? Czy jest tutaj, gdzie
friend
wchodzi?
Dodatkowe pytania:
- Czy w przypadku C ++ 11 powinienem oznaczyć moje
swap
snoexcept
? - Czy w przypadku C ++ 11 i jego range-for powinienem umieścić wewnątrz klasy
friend iter begin()
i wfriend iter end()
ten sam sposób? Myślę, żefriend
nie jest tu potrzebne, prawda?
c++
c++11
friend
copy-and-swap
towi
źródło
źródło
friend
funkcja w ogóle nie jest funkcją składową.Odpowiedzi:
Istnieje kilka sposobów pisania
swap
, niektóre lepsze niż inne. Z czasem jednak okazało się, że jedna definicja działa najlepiej. Zastanówmy się, jak moglibyśmy pomyśleć o napisaniuswap
funkcji.Najpierw widzimy, że kontenery
std::vector<>
mają jednoargumentową funkcję składowąswap
, taką jak:Oczywiście nasza klasa też powinna, prawda? No nie bardzo. Biblioteka standardowa zawiera różne niepotrzebne rzeczy , a
swap
jednym z nich jest członek . Czemu? Chodźmy dalej.To, co powinniśmy zrobić, to określić, co jest kanoniczne, a jaka nasza klasa musi zrobić, aby z tym pracować. Kanoniczna metoda zamiany to
std::swap
. Dlatego funkcje składowe nie są przydatne: generalnie nie są one sposobem, w jaki powinniśmy zamieniać rzeczy i nie mają wpływu na zachowaniestd::swap
.Cóż więc, aby wykonać
std::swap
pracę, powinniśmy zapewnić (istd::vector<>
powinniśmy zapewnić) specjalizacjęstd::swap
, prawda?Cóż, to z pewnością zadziałałoby w tym przypadku, ale wiąże się to z rażącym problemem: specjalizacje funkcji nie mogą być częściowe. Oznacza to, że nie możemy specjalizować klas szablonów w tym, tylko w określonych instancjach:
Ta metoda działa czasami, ale nie zawsze. Musi być lepszy sposób.
Jest! Możemy użyć
friend
funkcji i znaleźć ją przez ADL :Kiedy chcemy coś zamienić, kojarzymy †,
std::swap
a następnie wykonujemy połączenie bez zastrzeżeń:Co to jest
friend
funkcja? W tej dziedzinie panuje zamieszanie.Zanim C ++ został ustandaryzowany,
friend
funkcje wykonywały coś, co nazywa się „wstrzyknięciem nazwy znajomego”, gdzie kod zachowywał się tak, jakby funkcja została napisana w otaczającej przestrzeni nazw. Na przykład były to równoważne przed standardem:Jednak kiedy wynaleziono ADL, zostało to usunięte.
friend
Funkcja może wówczas tylko można znaleźć przez ADL; jeśli chcesz, aby była to funkcja wolna, musiała zostać zadeklarowana jako taka ( zobacz na przykład). Ale oto! Był problem.Jeśli po prostu użyjesz
std::swap(x, y)
, twoje przeciążenie nigdy nie zostanie znalezione, ponieważ wyraźnie powiedziałeś „szukajstd
i nigdzie indziej”! Dlatego niektórzy sugerowali napisanie dwóch funkcji: jednej jako funkcji do znalezienia przez ADL , a drugiej do obsługi jawnejstd::
kwalifikacji.Ale jak widzieliśmy, to nie działa we wszystkich przypadkach i kończy się brzydkim bałaganem. Zamiast tego idiomatyczna zamiana poszła inną drogą: zamiast sprawić, że zadaniem klas jest zapewnienie
std::swap
, zadaniem swapów jest upewnienie się, że nie używają wykwalifikowanychswap
, jak powyżej. I to zwykle działa całkiem nieźle, o ile ludzie o tym wiedzą. Ale w tym tkwi problem: nieintuicyjne jest korzystanie z połączenia bez kwalifikacji!Aby to ułatwić, niektóre biblioteki, takie jak Boost, udostępniły funkcję
boost::swap
, która po prostu wykonuje niekwalifikowane wywołanieswap
, zstd::swap
jako skojarzoną przestrzeń nazw. Pomaga to w przywróceniu zwięzłości, ale nadal jest kłopotliwe.Zauważ, że w C ++ 11 nie ma zmiany w zachowaniu
std::swap
, o którym my i inni błędnie myśleliśmy. Jeśli cię to ugryzło, przeczytaj tutaj .Krótko mówiąc: funkcja składowa jest po prostu szumem, specjalizacja jest brzydka i niepełna, ale
friend
funkcja jest kompletna i działa. A kiedy zamieniasz się, użyjboost::swap
lub niekwalifikowanyswap
zstd::swap
powiązanym.† Nieformalnie nazwa jest skojarzona, jeśli będzie brana pod uwagę podczas wywołania funkcji. Aby uzyskać szczegółowe informacje, przeczytaj §3.4.2. W tym przypadku
std::swap
zwykle nie jest brane pod uwagę; ale możemy go skojarzyć (dodać do zbioru przeciążeń rozpatrywanych przez niekwalifikowaneswap
), pozwalając na jego znalezienie.źródło
std::vector<std::string>().swap(someVecWithData);
, Co nie jest możliwe w przypadkuswap
funkcji wolnej, ponieważ oba argumenty są przekazywane przez odwołanie inne niż stała.operator=
,operator+
ioperator+=
, ale wyraźnie te podmioty na odpowiednich klasach są akceptowane / oczekiwanych istnieć dla symetrii. Moim zdaniem to samo dotyczy elementu członkowskiegoswap
i obszaru nazwswap
.function<void(A*)> f; if(!f) { }
może zawieść tylko dlatego, żeA
deklaruje,operator!
że akceptujef
równie dobrze jakf
własneoperator!
(mało prawdopodobne, ale może się zdarzyć). Gdybyfunction<>
autor pomyślał „och, mam 'operator bool', dlaczego miałbym zaimplementować 'operator!'? To naruszyłoby DRY!”, Byłoby to fatalne w skutkach. Musisz tylko miećoperator!
zaimplementowaną dlaA
iA
konstruktora dla afunction<...>
, a wszystko się zepsuje, ponieważ obaj kandydaci będą wymagać konwersji zdefiniowanych przez użytkownika.Ten kod jest odpowiednikiem ( prawie pod każdym względem):
Funkcja zaprzyjaźniona zdefiniowana w klasie to:
inline
Dokładne zasady są w sekcji
[class.friend]
(cytuję akapity 6 i 7 szkicu C ++ 0x):źródło
swap
jest ono widoczne tylko dla ADL. Jest członkiem otaczającej przestrzeni nazw, ale jego nazwa nie jest widoczna dla innych formularzy wyszukiwania nazw. EDYCJA: Widzę, że @GMan znów był szybszy :) @Ben zawsze tak było w ISO C ++ :)friend
funkcje są znalezione tylko przez ADL, a jeśli mają być po prostu wolnymi funkcjami zfriend
dostępem, muszą być zadeklarowane zarówno jakofriend
wewnątrz klasy, jak i jako normalna wolna deklaracja funkcji poza klasą. Na przykład w tej odpowiedzi widać tę konieczność .