Tornando a troca mais rápida, fácil de usar e protegida contra exceções

Eu não consegui dormir na noite passada e comecei a pensar emstd::swap. Aqui está a versão familiar do C ++ 98:

template <typename T>
void swap(T& a, T& b)
{
    T c(a);
    a = b;
    b = c;
}

Se uma classe definida pelo usuárioFoo&nbsp;usa recursos externos, isso é ineficiente. O idioma comum é fornecer um métodovoid Foo::swap(Foo& other)&nbsp;e uma especialização destd::swap<Foo>. Observe que isso não funciona com a classemodelos&nbsp;como você não pode especializar parcialmente um modelo de função e sobrecarregar nomes nostd&nbsp;espaço para nome é ilegal. A solução é escrever uma função de modelo no próprio espaço de nomes e confiar na pesquisa dependente de argumento para encontrá-la. Isso depende criticamente do cliente para seguir o "using std::swap&nbsp;idioma "em vez de chamarstd::swap&nbsp;diretamente. Muito quebradiço.

Em C ++ 0x, seFoo&nbsp;possui um construtor de movimentação definido pelo usuário e um operador de atribuição de movimentação, fornecendo umswap&nbsp;método e umstd::swap<Foo>&nbsp;especialização tem pouco ou nenhum benefício de desempenho, porque a versão C ++ 0x dostd::swap&nbsp;usa movimentos eficientes em vez de cópias:

#include <utility>

template <typename T>
void swap(T& a, T& b)
{
    T c(std::move(a));
    a = std::move(b);
    b = std::move(c);
}

Não ter que mexer comswap&nbsp;já remove bastante a carga do programador. Os compiladores atuais ainda não geram construtores de movimentação e operadores de atribuição de movimentação, mas, tanto quanto eu sei, isso mudará. O único problema que resta então é a segurança de exceção, porque, em geral, as operações de movimentação são permitidas, e isso abre uma lata inteira de worms. A pergunta "Qual é exatamente o estado de um objeto movido de"? complica ainda mais as coisas.

Então eu estava pensando, quais são exatamente as semânticas destd::swap&nbsp;em C ++ 0x se tudo correr bem? Qual é o estado dos objetos antes e depois da troca? Normalmente, a troca por operações de movimentação não toca nos recursos externos, apenas nas próprias representações de objetos "simples".

Então, por que não simplesmente escrever umswap&nbsp;modelo que faz exatamente isso:trocar as representações de objetos?

#include <cstring>

template <typename T>
void swap(T& a, T& b)
{
    unsigned char c[sizeof(T)];

    memcpy( c, &a, sizeof(T));
    memcpy(&a, &b, sizeof(T));
    memcpy(&b,  c, sizeof(T));
}

É o mais eficiente possível: simplesmente explode através da memória bruta. Ele não requer nenhuma intervenção do usuário: nenhum método especial de troca ou operação de movimentação precisa ser definido. Isso significa que ele ainda funciona em C ++ 98 (que não possui referências de rvalue, veja bem). Mas ainda mais importante,agora podemos esquecer as questões de segurança de exceção, Porquememcpy&nbsp;nunca joga.

Eu posso ver dois problemas em potencial com essa abordagem:

Primeiro, nem todos os objetos devem ser trocados. Se um designer de classe ocultar o construtor de cópias ou o operador de atribuição de cópias, a tentativa de trocar objetos da classe deverá falhar no momento da compilação. Podemos simplesmente introduzir um código morto que verifica se a cópia e a atribuição são legais no tipo:

template <typename T>
void swap(T& a, T& b)
{
    if (false)    // dead code, never executed
    {
        T c(a);   // copy-constructible?
        a = b;    // assignable?
    }

    unsigned char c[sizeof(T)];

    std::memcpy( c, &a, sizeof(T));
    std::memcpy(&a, &b, sizeof(T));
    std::memcpy(&b,  c, sizeof(T));
}

Qualquer compilador decente pode se livrar trivialmente do código morto. (Provavelmente existem maneiras melhores de verificar a "conformidade de troca", mas esse não é o ponto. O que importa é que é possível).

Segundo, alguns tipos podem executar ações "incomuns" no construtor de cópias e no operador de atribuição de cópias. Por exemplo, eles podem notificar os observadores sobre suas alterações. Considero isso um problema menor, porque esses tipos de objetos provavelmente não deveriam ter fornecido operações de cópia em primeiro lugar.

Por favor, deixe-me saber o que você acha dessa abordagem à troca. Funcionaria na prática? Você usaria? Você pode identificar os tipos de bibliotecas onde isso seria interrompido? Você vê problemas adicionais? Discutir!