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 napisaniu swap
funkcji.
Najpierw widzimy, że kontenery std::vector<>
mają jednoargumentową funkcję składową swap
, taką jak:
struct vector
{
void swap(vector&) { /* swap members */ }
};
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 zachowanie std::swap
.
Cóż więc, aby wykonać std::swap
pracę, powinniśmy zapewnić (i std::vector<>
powinniśmy zapewnić) specjalizacjęstd::swap
, prawda?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
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:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
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 :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Kiedy chcemy coś zamienić, kojarzymy †, std::swap
a następnie wykonujemy połączenie bez zastrzeżeń:
using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first
// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap
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:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
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ś „szukaj std
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łanie swap
, 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żyj boost::swap
lub niekwalifikowany swap
z std::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 niekwalifikowane swap
), pozwalając na jego znalezienie.