14 maja 2016

XOR Swap jest złe!

Trick XOR swap

Kilka słów o pewnym triku, który jest bardzo zły!

Mamy napisać funkcję, która zamienia miejscami dwie zmienne (funkcje swap). Nic prostszego:

auto tmp = x;
x = y;
y = tmp;

A teraz załóżmy, że mamy dodatkowe wymaganie, aby nie korzystać ze zmiennej tymczasowej, czyli swap w miejscu. Jako przykład rozwiązania takiego problemu podaje się poniższy kod:

x = x xor y;
y = x xor y;
x = x xor y;

Podany wyżej kod pojawia się w wielu sytuacjach: jako ciekawostka, jako trik oraz w pytaniach rekrutacyjnych. I niby wszystko funkcjonuje poprawnie. Podstawiamy dwie liczby i działa:

#include <iostream>

using namespace std;
int main() {
    int x=10, y=50;

    x = x xor y;
    y = x xor y;
    x = x xor y;

    cout << "x = " << x <<endl;
    cout << "y = " << y <<endl;
}

Wynik:

x = 50
y = 10

Dlaczego xor swap jest złe

Co wiec jest złego z xor_swap? A czy dwie podane niżej funkcje działają identycznie?

template<typename T>
void Xor_Swap( T& x, T& y ) noexcept
{
    x = x xor y;
    y = x xor y;
    x = x xor y;
}

template<typename T>
void TempSwap( T& x, T& y ) noexcept
{
    auto tmp = x;
    x = y;
    y = tmp;
}

Problem jest taki, że gdy przekazane referencje będą odnosić się do tego samego obszaru pamięci, wówczas funkcja xor_swap nam ten obszar pamięci wyzeruje! Podane powyżej dwie implementacje nie są tożsame. Dla typów wartościowych faktycznie ich działanie jest identyczne, ale dla typów wskaźnikowych istnieje przypadek szczególny.

int main() {
    vector<int> data1 = {1,2,3};
    vector<int> data2 = {1,2,3};

    TempSwap(data1[0],data1[2]);
    Xor_Swap(data2[0],data2[2]);  //Działa poprawnie

    cout << "data1: " << data1[0] << ",  " << data1[1] << ",  " << data1[2] << endl;
    cout << "data2: " << data2[0] << ",  " << data2[1] << ",  " << data2[2] << endl;

    cout << "======== "  << endl;

    TempSwap(data1[0],data1[0]);
    Xor_Swap(data2[0],data2[0]);  //Tu jest problem

    cout << "data1: " << data1[0] << ",  " << data1[1] << ",  " << data1[2] << endl;
    cout << "data2: " << data2[0] << ",  " << data2[1] << ",  " << data2[2] << endl;
}
data1: 3,  2,  1
data2: 3,  2,  1
========
data1: 3,  2,  1
data2: 0,  2,  1