como fornecer uma função de troca para minha classe?

87

Qual é a maneira correta de habilitar meus swapalgoritmos em STL?

1) Membro swap. Does std::swapusar truque SFINAE de usar o membro swap.

2) Autonomia swapno mesmo namespace.

3) Especialização parcial de std::swap.

4) Todas as opções acima.

Obrigado.

EDIT: Parece que eu não fiz minha pergunta claramente. Basicamente, eu tenho uma classe de modelo e preciso de algoritmos STL para usar o método de troca (eficiente) que escrevi para essa classe.

pic11
fonte

Respostas:

94
  1. é o uso adequado de swap. Escreva desta forma quando você escreve o código de "biblioteca" e deseja habilitar ADL (pesquisa dependente de argumento) swap. Além disso, isso não tem nada a ver com 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. É a maneira correta de fornecer uma swapfunção para sua classe.
namespace Foo {

class Bar{}; // dummy

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

}

Se swapagora for usado como mostrado em 1), sua função será encontrada. Além disso, você pode tornar essa função um amigo se for absolutamente necessário, ou fornecer um membro swapque é chamado pela função gratuita:

// 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. Você quer dizer uma especialização explícita. Parcial ainda é outra coisa e também não é possível para funções, apenas structs / classes. Como tal, desde que você não pode se especializar std::swappara classes de modelo, você tem de fornecer uma função gratuita no seu namespace. Não é uma coisa ruim, se assim posso dizer. Agora, uma especialização explícita também é possível, mas geralmente você não deseja especializar um modelo de função :
namespace std
{  // only allowed to extend namespace std with specializations

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

}
  1. Não, visto que 1) é diferente de 2) e 3). Além disso, ter 2) e 3) levará a sempre 2) escolhido, porque se ajusta melhor.
Xeo
fonte
8
Sua (1) e a (1) da pergunta não se alinham, a menos que eu esteja interpretando algo errado. Ainda assim, +1
Dennis Zickefoose
1
@Xeo. Obrigado por sua contribuição. Eu editei minha pergunta. O STL usa swap como você descreveu no caso 1?
pic11
1
@pic: Sim, o STL usará a troca ADL que mostrei em 1), mas apenas se estiver lá como uma função livre, não apenas uma função de membro. Veja 2) e 3), ambas as versões serão escolhidas por algoritmos. Eu aconselharia 2), como 3) está desatualizado e é considerado uma prática ruim.
Xeo
2
O comentário na primeira parte do código é enganoso. using std::swap;não ativa o ADL, apenas permite que o compilador localize std::swapse o ADL não encontrou uma sobrecarga adequada.
David Rodríguez - dribeas
6
Esta resposta é tecnicamente correta, mas necessita urgentemente de edição para maior clareza. O OP (1) não é a resposta correta, uma vez que uma leitura excessivamente rápida nesta resposta parece indicar erroneamente.
Howard Hinnant,
1

Para responder ao EDIT, onde as classes podem ser classes de modelo, você não precisa de especialização. considere uma aula como esta:

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

você pode definir classes como:

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

se você quiser ser capaz de criar uma função para implementar todas as 3 trocas (não que esta classe de exemplo justifique), você faz exatamente como Xeo disse em (2) ... sem especialização, mas apenas faça uma função de modelo regular:

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);
}

A função de modelo de troca deve estar localizada no mesmo namespace da classe que você está tentando trocar. o método a seguir encontrará e usará essa troca, mesmo que você não esteja fazendo referência a esse namespace usando ADL:

using std::swap;
swap(a,b);
Ben
fonte
0

Parece que (2) ( independente swapno mesmo namespace onde a classe definida pelo usuário é declarada ) é a única maneira permitida de fornecer swapuma classe definida pelo usuário, porque adicionar declarações ao namespace stdé geralmente um comportamento indefinido. Estendendo o namespace std (cppreference.com) :

É um comportamento indefinido adicionar declarações ou definições ao namespace stdou a qualquer namespace aninhado std, com algumas exceções observadas abaixo

E swapnão é denotado como uma dessas exceções. Portanto, adicionar sua própria swapsobrecarga ao stdnamespace é um comportamento indefinido.

Também é dito que a biblioteca padrão usa uma chamada não qualificada para a swapfunção a fim de chamar definido swappelo usuário para uma classe de usuário, se tal definido pelo usuário swapfor fornecido.

Swappable (cppreference.com) :

Muitas funções de biblioteca padrão (por exemplo, muitos algoritmos) esperam que seus argumentos satisfaçam Swappable , o que significa que sempre que a biblioteca padrão realiza uma troca, ela usa o equivalente de using std::swap; swap(t, u);.

swap (www.cplusplus.com) :

Muitos componentes da biblioteca padrão (dentro std) chamada swapem uma inqualificável forma a permitir sobrecargas personalizadas para tipos não-fundamentais para ser chamado em vez desta versão genérica: sobrecargas personalizados de swapdeclarada no mesmo espaço de nomes como o tipo para o qual são fornecidas selecionado por meio de pesquisa dependente de argumento sobre esta versão genérica.

Mas observe que usar diretamente a std::swapfunção para uma classe definida pelo usuário chama a versão genérica de em std::swapvez da definida pelo usuário swap:

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

Portanto, é recomendável chamar a swapfunção no código do usuário da mesma forma que é feito na biblioteca padrão:

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
fonte