Como sobrecarregar std::swap()
-
08-06-2019 - |
Pergunta
std::swap()
é usado por muitos contêineres padrão (como std::list
e std::vector
) durante a classificação e até mesmo a atribuição.
Mas a implementação padrão de swap()
é muito generalizado e bastante ineficiente para tipos personalizados.
Assim, a eficiência pode ser obtida sobrecarregando std::swap()
com uma implementação específica de tipo personalizado.Mas como você pode implementá-lo para que seja usado pelos contêineres padrão?
Solução
A maneira correta de sobrecarregar o swap é escrevê-lo no mesmo namespace do que você está trocando, para que possa ser encontrado via pesquisa dependente de argumento (ADL).Uma coisa particularmente fácil de fazer é:
class X
{
// ...
friend void swap(X& a, X& b)
{
using std::swap; // bring in swap for built-in types
swap(a.base1, b.base1);
swap(a.base2, b.base2);
// ...
swap(a.member1, b.member1);
swap(a.member2, b.member2);
// ...
}
};
Outras dicas
Atenção Mozza314
Aqui está uma simulação dos efeitos de um genérico std::algorithm
ligando std::swap
, e fazer com que o usuário forneça sua troca no namespace std.Como se trata de um experimento, esta simulação usa namespace exp
em vez de namespace std
.
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
exp::swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
namespace exp
{
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
Para mim isso imprime:
generic exp::swap
Se o seu compilador imprimir algo diferente, ele não estará implementando corretamente a "pesquisa em duas fases" para modelos.
Se o seu compilador estiver em conformidade (com qualquer C++ 98/03/11), ele fornecerá a mesma saída que mostro.E nesse caso, exatamente o que você teme que aconteça, acontece.E colocando o seu swap
no espaço para nome std
(exp
) não impediu que isso acontecesse.
Dave e eu somos membros do comitê e trabalhamos nesta área do padrão há uma década (e nem sempre em acordo um com o outro).Mas esta questão já foi resolvida há muito tempo e ambos concordamos na forma como foi resolvida.Desconsidere a opinião/resposta do especialista de Dave nesta área por sua própria conta e risco.
Esse problema veio à tona após a publicação do C++ 98.A partir de 2001, Dave e eu começamos a trabalhar esta área.E esta é a solução moderna:
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
void swap(A&, A&)
{
printf("swap(A, A)\n");
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
A saída é:
swap(A, A)
Atualizar
Foi feita uma observação que:
namespace exp
{
template <>
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
funciona!Então por que não usar isso?
Considere o caso em que seu A
é um modelo de classe:
// simulate user code which includes <algorithm>
template <class T>
struct A
{
};
namespace exp
{
template <class T>
void swap(A<T>&, A<T>&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A<int> a[2];
exp::algorithm(a, a+2);
}
Agora não funciona novamente.:-(
Então você poderia colocar swap
no namespace std e faça funcionar.Mas você precisará se lembrar de colocar swap
em A
namespace para o caso quando você tem um modelo: A<T>
.E como ambos os casos funcionarão se você colocar swap
em A
no namespace, é mais fácil lembrar (e ensinar os outros) de fazer isso dessa maneira.
Você não tem permissão (pelo padrão C++) para sobrecarregar std::swap, no entanto, você tem permissão específica para adicionar especializações de modelo para seus próprios tipos ao namespace std.Por exemplo.
namespace std
{
template<>
void swap(my_type& lhs, my_type& rhs)
{
// ... blah
}
}
então os usos nos contêineres std (e em qualquer outro lugar) escolherão sua especialização em vez da geral.
Observe também que fornecer uma implementação de classe base de swap não é bom o suficiente para seus tipos derivados.Por exemplo.se você tem
class Base
{
// ... stuff ...
}
class Derived : public Base
{
// ... stuff ...
}
namespace std
{
template<>
void swap(Base& lha, Base& rhs)
{
// ...
}
}
isso funcionará para classes Base, mas se você tentar trocar dois objetos Derivados ele usará a versão genérica do std porque a troca modelada é uma correspondência exata (e evita o problema de trocar apenas as partes 'base' de seus objetos derivados ).
OBSERVAÇÃO:Atualizei isso para remover os bits errados da minha última resposta.Ah!(obrigado puetzk e j_random_hacker por apontar isso)
Embora seja correto que geralmente não se deve adicionar coisas ao std::namespace, é especificamente permitido adicionar especializações de modelo para tipos definidos pelo usuário.Sobrecarregar as funções não é.Esta é uma diferença sutil :-)
17.4.3.1/1 É indefinido para um programa C ++ para adicionar declarações ou definições ao espaço para nome STD ou namespaces com namespace std, a menos que especificado de outra forma.Um programa pode adicionar especializações de modelo para qualquer modelo de biblioteca padrão ao espaço para nome STD.Essa especialização (completa ou parcial) de uma biblioteca padrão resulta em comportamento indefinido, a menos que a declaração dependa de um nome definido pelo usuário da ligação externa e, a menos que a especialização do modelo atenda aos requisitos de biblioteca padrão para o modelo original.
Uma especialização de std::swap seria semelhante a:
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
Sem o bit template<> seria uma sobrecarga, que é indefinida, em vez de uma especialização, que é permitida.A abordagem sugerida por @Wilka para alterar o namespace padrão pode funcionar com o código do usuário (devido à pesquisa de Koenig preferir a versão sem namespace), mas não é garantido que isso aconteça e, na verdade, não é suposto que funcione (a implementação STL deve usar o totalmente -std qualificado::swap).
Existe um tópico em comp.lang.c++.moderated com um longo discussão do tema.A maior parte é sobre especialização parcial (o que atualmente não há uma boa maneira de fazer).