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 swapfunkcji.
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 swapjednym 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::swappracę, 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ć friendfunkcji 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, friendfunkcje 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. friendFunkcja 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 stdi 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 friendfunkcja jest kompletna i działa. A kiedy zamieniasz się, użyj boost::swaplub niekwalifikowany swapz std::swappowią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::swapzwykle nie jest brane pod uwagę; ale możemy go skojarzyć (dodać do zbioru przeciążeń rozpatrywanych przez niekwalifikowane swap), pozwalając na jego znalezienie.