2010-01-05 20 views
9

W poniższym przykładzie oczekiwałem wymiany bitów. Zamiast tego drugi bit zostaje nadpisany, ale dlaczego i jak mogę osiągnąć oczekiwane zachowanie?Dlaczego std :: swap bitów w instancji std :: bitset nie działa?

#include <iostream> 
#include <string> 
#include <algorithm> 

using namespace std; 

int main() 
{ 
    bitset<2> test(string("10")); 
    cout << test; // Prints "10" 
    swap(test[0], test[1]); 
    cout << test; // Prints "11", why not "01"? 
} 

Odpowiedz

13

To jest bardzo nieprzyjemne. Najpierw musimy spojrzeć na deklaracji wymiany:

template<class T> 
void swap(T &left, T &right); 

Teraz operator[]() na bitset ma dwa przeciążeń:

bool operator[](size_type _Pos) const; 
reference operator[](size_type _Pos); 

Tutaj reference jest bitset::reference, zagnieżdżone klasy w bitset który skutecznie działa jako serwer proxy odniesienie do jednego z podstawowych bitów. To, co obejmuje, to bitset i pozycja w bitset. Z powodu deklaracji swap, drugie przeciążenie jest wybierane, a my zamieniamy dwa bitset::reference s. Teraz jest tutaj, gdzie robi się nieprzyjemnie. Spójrzmy na typowy realizacji swapu:

template class<T> swap(T &left, T &right) { 
    T temp = left; 
    left = right; 
    right = temp; 
} 

Problemem jest to, że left i right są zarówno odniesienia do bitset::reference. Mają te same podstawowe dane (ponieważ są one serwerami proxy, to samo oznacza, że ​​oba wskazują na to samo bitset!) Po prostu zawierają różne pozycje w tym bitset.Dlatego myśl o tym tak: left to pozycja 0 w niektórych bitset i right jest pozycja 1 w niektórych bitset i że bitset jest taki sam bitset jak left! Odwróćmy się na zawsze do tego bitset jako BS (wybrane celowo).

Więc

T temp = left; 

mówi, że temp jest pozycja 0 w BS.

left = right; 

zestawy pozycji 0 w lewo do pozycji 1 w BS (co jednocześnie zmienia pozycję 0 w temp!)

right = temp; 

zestawy pozycji 1 w prawo do pozycji 0 w BS (który został właśnie ustawiony pozycja 1 w BS!). Tak więc na końcu tego bałaganu ma się ta pozycja 0, niezależnie od pozycji 1, a pozycja 1 pozostaje niezmieniona! Teraz, ponieważ pozycja 0 to LSB, a pozycja 1 to MSB, to "10" staje się "11". Brzydki.

Można to obejść za pomocą template specialization:

namespace std { 
    template<> 
    void swap<bitset<2>::reference>(
     bitset<2>::reference &left, 
     bitset<2>::reference &right 
    ) { 
     bool temp = (bool)left; 
     left = (bool)right; 
     right = (bool)temp; 
    } 
} 

Następnie:

int main() { 
    bitset<2> test(string("10")); 
    cout << test; // Prints "10" 
    swap(test[0], test[1]); 
    cout << test; // Prints "01", hallelujah! 
} 
+1

Dobra robota! Niewiarygodne, że nawet C++ 0x w ogóle wspomina o 'swap' w stosunku do bitsetów. – Potatoswatter

+1

Czy to faktycznie się kompiluje? 'test [0]' jest tymczasowy, którego nie można odwołać się do ... – Barry

2

Właściwie od test[i] Zwraca rvalue odniesienia bitset, ja naprawdę nie rozumiem, jak można skompilować swap tutaj. Mój kompilator (g ++ 4.3.3) mówi mi:

test.cpp:12: error: no matching function for call to 
    'swap(std::bitset<2u>::reference, std::bitset<2u>::reference)' 
/usr/include/c++/4.3/bits/stl_move.h:80: note: candidates are: 
    void std::swap(_Tp&, _Tp&) [with _Tp = std::bitset<2u>::reference] 
+2

Tak. MSVC akceptuje kod, nic dobrego nie dzieje się w swap(). –

+1

@HansPassant VS akceptuje ten kod z powodu [błędu] (https://connect.microsoft.com/VisualStudio/feedback/details/775818/vc11-non-const-lvalue-reference-incorrectly-binds-to-rvalue) co pozwala związkom non-const lvalue na powiązanie z wartościami r. Poprawiłem błąd (pomagając przekonać Microsoft, żeby to naprawił) i dodałem link do tego posta jako przykład nieprzyjemnych konsekwencji tego błędu. Proponuję (wszyscy), aby również upublicznić błąd. –

2

Nie ma typ wartości w C++ do reprezentowania pojedynczego bitu, więc podczas korzystania z operatorem [] aby uzyskać dostęp do elementu o bitset, co masz jest Obiekt proxy, który służy jako alias dla żądanego bitu. Przypisanie do tego obiektu proxy zmienia odpowiednią wartość bitową w oryginalnym obiekcie zestawu bitów.

Jak pokazuje Victor's answer, Twój kod nie kompiluje się z GCC. Ale załóżmy, że połączenie do swap zostanie skompilowane. Można by dostać kod Stanowi to mniej więcej tak:

void swap(std::bitset<2>::reference& a, std::bitset<2>::reference& b) 
{ 
    std::bitset<2>::reference tmp = a; 
    a = b; 
    b = tmp; 
} 

Deklaracja tmp inicjalizuje zmienną a, ale to nie robi kopię bitu. Zamiast tego tworzy kopię obiektu proxy, więc odnosi się do tego samego bitu w tym samym zestawie bitów, którego dotyczy a. Następny wiersz przypisuje b do a, który kopiuje wartość bitu z lokalizacji a i zapisuje go w lokalizacji bitowej, do której odnosi się b. Wreszcie istnieje zadanie tmp w b. Ale pamiętaj, że tmp nadal odnosi się do bitu, którego dotyczy a. Czytając tmp jest taka sama jak czytanie a, więc ostatecznie uzyskać ten sam efekt można dostać jeśli swap były tylko te dwie linie:

a = b; 
b = a; 

W kodzie a jest 0 i b wynosi 1, więc z tymi, dwa stwierdzenia dotyczące przypisania, 11 jest dokładnie tym, czego moglibyśmy się spodziewać.

Powiązane problemy