jak zapewnić funkcję wymiany dla mojej klasy?

89

Jaki jest właściwy sposób włączenia moich swapalgorytmów w STL?

1) Członek swap. Czy std::swapużywać SFINAE podstęp używać elementu swap.

2) Wolnostojący swapw tej samej przestrzeni nazw.

3) Częściowa specjalizacja std::swap.

4) Wszystkie powyższe.

Dziękuję Ci.

EDYCJA: Wygląda na to, że nie sformułowałem jasno mojego pytania. Zasadniczo mam klasę szablonu i potrzebuję alg STL, aby użyć (wydajnej) metody wymiany, którą napisałem dla tej klasy.

pic11
źródło

Odpowiedzi:

95
  1. jest właściwe korzystanie z swap. Napisz to w ten sposób, kiedy piszesz kod „biblioteki” i chcesz włączyć ADL (wyszukiwanie zależne od argumentów) swap. Nie ma to też nic wspólnego z SFINAE.
// some algorithm in your code
template<class T>
void foo(T& lhs, T& rhs) {
    using std::swap; // enable 'std::swap' to be found
                    // if no other 'swap' is found through ADL
    // some code ...
    swap(lhs, rhs); // unqualified call, uses ADL and finds a fitting 'swap'
                    // or falls back on 'std::swap'
    // more code ...
}
  1. To właściwy sposób na zapewnienie swapfunkcji dla Twojej klasy.
namespace Foo {

class Bar{}; // dummy

void swap(Bar& lhs, Bar& rhs) {
    // ...
}

}

Jeśli swapjest teraz używany, jak pokazano w 1), Twoja funkcja zostanie znaleziona. Możesz również zaprzyjaźnić się z tą funkcją, jeśli jest to absolutnie konieczne, lub zapewnić element członkowski swapwywoływany przez funkcję bezpłatną:

// version 1
class Bar{
public:
    friend void swap(Bar& lhs, Bar& rhs) {
    // ....
    }
};

// version 2
class Bar{
public:
    void swap(Bar& other) {
    // ...
    }
};

void swap(Bar& lhs, Bar& rhs) {
    lhs.swap(rhs);
}

...
  1. Masz na myśli wyraźną specjalizację. Częściowe jest nadal czymś innym i również nie jest możliwe w przypadku funkcji, tylko struktury / klasy. Jako taki, ponieważ nie można specjalizować std::swapdla klas szablon, mają zapewnić bezpłatną funkcję w swojej przestrzeni nazw. Nieźle, jeśli mogę tak powiedzieć. Teraz możliwa jest również jawna specjalizacja, ale generalnie nie chcesz specjalizować się w szablonie funkcji :
namespace std
{  // only allowed to extend namespace std with specializations

template<> // specialization
void swap<Bar>(Bar& lhs, Bar& rhs) noexcept {
    // ...
}

}
  1. Nie, ponieważ 1) różni się od 2) i 3). Ponadto posiadanie zarówno 2), jak i 3) spowoduje, że zawsze będziesz mieć 2) wybrane, ponieważ lepiej pasuje.
Xeo
źródło
8
Twój (1) i pytanie (1) tak naprawdę nie zgadzają się, chyba że coś źle czytam. Wciąż +1
Dennis Zickefoose
1
@Xeo. Dzięki za wkład. Zredagowałem moje pytanie. Czy STL używa zamiany, jak to opisałeś w przypadku 1?
pic11
1
@pic: Tak, STL użyje wymiany ADL, którą pokazałem w 1), ale tylko wtedy, gdy jest dostępna jako funkcja wolna, a nie tylko funkcja składowa. Patrz 2) i 3), obie wersje zostaną wybrane przez algorytmy. Radziłbym 2), ponieważ 3) jest przestarzały i uważany za złą praktykę.
Xeo,
2
Komentarz w pierwszym fragmencie kodu wprowadza w błąd. using std::swap;nie włącza ADL, po prostu pozwala kompilatorowi zlokalizować, std::swapjeśli ADL nie znajdzie odpowiedniego przeciążenia.
David Rodríguez - dribeas
6
Ta odpowiedź jest technicznie poprawna, ale bardzo potrzebuje edycji dla przejrzystości. PO (1) nie jest poprawną odpowiedzią, ponieważ zbyt szybki odczyt w tej odpowiedzi wydaje się błędnie wskazywać.
Howard Hinnant
1

Aby odpowiedzieć na EDYCJĘ, gdzie klasy mogą być klasami szablonowymi, w ogóle nie potrzebujesz specjalizacji. rozważ taką klasę:

template <class T>
struct vec3
{
    T x,y,z;
};

możesz zdefiniować takie klasy jak:

vec3<float> a;
vec3<double> b;
vec3<int> c;

jeśli chcesz mieć możliwość stworzenia jednej funkcji do zaimplementowania wszystkich 3 zamian (nie żeby ta przykładowa klasa to uzasadniała), robisz tak, jak powiedział Xeo w (2) ... bez specjalizacji, ale po prostu utwórz zwykłą funkcję szablonu:

template <class T>
void swap(vec3<T> &a, vec3<T> &b)
{
    using std::swap;
    swap(a.x,b.x);
    swap(a.y,b.y);
    swap(a.z,b.z);
}

Funkcja szablonu wymiany powinna znajdować się w tej samej przestrzeni nazw, co klasa, którą próbujesz zamienić. następująca metoda znajdzie i użyje tej zamiany, nawet jeśli nie odwołujesz się do tej przestrzeni nazw za pomocą ADL:

using std::swap;
swap(a,b);
Ben
źródło
0

Wydaje się, że (2) ( wolnostojący swapw tej samej przestrzeni nazw, w której zadeklarowana jest klasa zdefiniowana przez użytkownika ) jest jedynym dozwolonym sposobem zapewnienia swapklasy zdefiniowanej przez użytkownika, ponieważ dodawanie deklaracji do przestrzeni nazw stdjest zwykle niezdefiniowanym zachowaniem. Rozszerzenie przestrzeni nazw std (cppreference.com) :

Dodawanie deklaracji lub definicji do przestrzeni nazw stdlub do dowolnej przestrzeni nazw zagnieżdżonej w niej jest niezdefiniowanym zachowaniem std, z kilkoma wyjątkami wymienionymi poniżej

I swapnie jest oznaczony jako jeden z tych wyjątków. Zatem dodanie własnego swapprzeciążenia do stdprzestrzeni nazw jest niezdefiniowanym zachowaniem.

Mówi się również, że biblioteka standardowa używa niekwalifikowanego wywołania swapfunkcji w celu wywołania zdefiniowanej swapprzez użytkownika klasy użytkownika, jeśli taka zdefiniowana przez użytkownikaswap jest dostarczona.

Możliwość wymiany (cppreference.com) :

Wiele funkcji bibliotek standardowych (na przykład wiele algorytmów) oczekuje, że ich argumenty spełnią wymagania Swappable , co oznacza, że ​​za każdym razem, gdy biblioteka standardowa wykonuje zamianę, używa odpowiednika using std::swap; swap(t, u);.

swap (www.cplusplus.com) :

Wiele składników biblioteki standardowej (w ramach std) wywołuje swapw niekwalifikowany sposób, aby umożliwić wywoływanie niestandardowych przeciążeń dla typów innych niż podstawowe zamiast tej wersji ogólnej: Niestandardowe przeciążenia swapzadeklarowane w tej samej przestrzeni nazw, co typ, dla którego są dostarczane, są wybierane poprzez wyszukiwanie zależne od argumentów w tej wersji ogólnej.

Zwróć jednak uwagę, że bezpośrednie użycie std::swapfunkcji dla klasy zdefiniowanej przez użytkownika wywołuje ogólną wersję std::swapzamiast zdefiniowanej przez użytkownika swap:

my::object a, b;
std::swap(a, b); // calls std::swap, not my::swap

Dlatego zalecane jest wywołanie swapfunkcji w kodzie użytkownika w taki sam sposób, jak to się robi w standardowej bibliotece:

my::object a, b;
using std::swap;
swap(a, b); // calls my::swap if it is defined, or std::swap if it is not.
anton_rh
źródło