Existem várias maneiras de escrever swap
, algumas melhores que outras. Com o tempo, porém, verificou-se que uma única definição funciona melhor. Vamos considerar como podemos pensar em escrever uma swap
função.
Primeiro, vemos que contêineres std::vector<>
possuem uma função de membro de argumento único swap
, como:
struct vector
{
void swap(vector&) { /* swap members */ }
};
Naturalmente, então, nossa classe também deveria, certo? Bem, na verdade não. A biblioteca padrão tem todo tipo de coisas desnecessárias e um membro swap
é um deles. Por quê? Vamos continuar.
O que devemos fazer é identificar o que é canônico e o que nossa classe precisa fazer para trabalhar com ela. E o método canônico de troca é com std::swap
. É por isso que as funções membro não são úteis: elas não são como devemos trocar as coisas, em geral, e não têm influência no comportamento de std::swap
.
Bem, então, para fazer o std::swap
trabalho, devemos fornecer (e std::vector<>
deveria ter fornecido) uma especialização std::swap
, certo?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
Bem, isso certamente funcionaria neste caso, mas tem um problema evidente: as especializações de funções não podem ser parciais. Ou seja, não podemos especializar classes de modelo com isso, apenas instanciações específicas:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
Esse método funciona algumas vezes, mas não o tempo todo. Deve haver uma maneira melhor.
Há sim! Podemos usar uma friend
função e encontrá-la através da ADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Quando queremos trocar alguma coisa, associamos † std::swap
e, em seguida, fazemos uma chamada não qualificada:
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
O que é uma friend
função? Há confusão nessa área.
Antes da padronização do C ++, as friend
funções executavam algo chamado "injeção de nome de amigo", em que o código se comportava como se a função tivesse sido escrita no espaço para nome ao redor. Por exemplo, esses eram pré-padrão equivalente:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
No entanto, quando a ADL foi inventada, essa foi removida. A friend
função só poderia ser encontrada via ADL; se você a quisesse como uma função livre, ela precisava ser declarada como tal ( veja isso , por exemplo). Mas eis! Havia um problema.
Se você apenas usar std::swap(x, y)
, sua sobrecarga nunca será encontrada, porque você disse explicitamente "procure dentro std
e em nenhum outro lugar"! É por isso que algumas pessoas sugeriram escrever duas funções: uma como uma função que pode ser encontrada via ADL e a outra para lidar com std::
qualificações explícitas .
Mas, como vimos, isso não pode funcionar em todos os casos, e acabamos com uma bagunça feia. Em vez disso, a troca idiomática seguiu o outro caminho: em vez de tornar o trabalho das classes fornecer std::swap
, é trabalho dos trocadores garantir que eles não usem qualificada swap
, como acima. E isso tende a funcionar muito bem, desde que as pessoas saibam disso. Mas aí está o problema: não é intuitivo precisar usar uma chamada não qualificada!
Para facilitar isso, algumas bibliotecas como o Boost forneceram a função boost::swap
, que apenas faz uma chamada não qualificada swap
, com std::swap
um espaço de nome associado. Isso ajuda a tornar as coisas sucintas novamente, mas ainda é uma chatice.
Observe que não há alteração no C ++ 11 no comportamento de std::swap
, que eu e outros pensamos erroneamente que seria o caso. Se você foi mordido por isso, leia aqui .
Em resumo: a função membro é apenas ruído, a especialização é feia e incompleta, mas a friend
função está completa e funciona. E quando você trocar, use boost::swap
ou um não qualificadoswap
com std::swap
associado.
† Informalmente, um nome é associado se for considerado durante uma chamada de função. Para detalhes, leia §3.4.2. Nesse caso, std::swap
normalmente não é considerado; mas podemos associá- lo (adicione-o ao conjunto de sobrecargas consideradas não qualificadas swap
), permitindo que seja encontrado.