14.10 Pola bitowe

Składowymi klas mogą być też pola bitowe. Są to składowe klasy o długości wyrażonej w bitach. Ich wartościami są liczby całkowite zapisane (w układzie dwójkowym) w zadeklarowanej ilości bitów. Oczywiście wynika z tego, że zakres tych wartości zależy od zadeklarowanej ilości bitów. Na przykład, deklarując pole bitowe bez znaku o długości 3, możemy tam zapisywać dane całkowite z zakresu [0, 7], bo istnieje osiem zero-jedynkowych kombinacji trzech bitów:

     7    6     5     4     3    2     1     0
    111  110   101   100   011  010   001   000
Można deklarować pola bitowe tylko typu int: ze znakiem (signed) lub bez znaku(unsigned) — podobnie jak dla normalnych typów całkowitych. Liczbę bitów przeznaczonych dla danego pola deklarujemy po dwukropku; na przykład po
       struct A {
           unsigned kolor : 4;
           // ...
       };
w klasie A istnieje składowa kolor będąca polem bitowym bez znaku o długości czterech bitów. Można zatem tej składowej przypisywać wartości z zakresu [0, 15]. Dlaczego nie użyliśmy zwykłego typu int bez określania ilości bitów? Otóż chodzi o to, że jeśli w klasie występuje kilka pól bitowych, kompilator zadba o to, aby je wszystkie upakować jak najgęściej. Jeśli zatem w klasie mamy pola bitowe o długościach 4, 7 i 3, to, ponieważ jedna liczba typu int zajmuje 32 bity, wszystkie zmieszczą sie w jednym int'cie! Uzyskujemy zatem oszczędność pamięci i mamy możliwość kodowania kilku różnych informacji w pojedynczej fizycznie danej.

W poniższym przykładzie definiujemy klasę opisującą rodzaj czcionki: w polach bitowych przechowujemy informację o kroju, wadze i kolorze czcionki:


P108: polbit.cpp     Pola bitowe

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  class Czcionka {
      5.      unsigned  kroj : 3;
      6.      unsigned  waga : 1;
      7.      unsigned kolor : 2;
      8.  public:
      9.      enum  Kroj { HELVETICA, TIMES, ARIAL,      
     10.                   COURIER, BOOKMAN, SYMBOL};
     11.      enum  Waga { NORMAL, BOLD };
     12.      enum Kolor { BLACK, RED, GREEN, BLUE };
     13.  
     14.      Czcionka(Kroj kroj, Waga waga, Kolor kolor) {
     15.          this->kroj  = kroj;
     16.          this->waga  = waga;
     17.          this->kolor = kolor;
     18.      }
     19.  
     20.      void opis() {
     21.          cout << "Kroj nr " << kroj << "; Waga nr "
     22.               << waga << "; Kolor nr " << kolor << endl;
     23.      }
     24.  };
     25.  
     26.  int main() {
     27.      Czcionka tytul(Czcionka::ARIAL,  Czcionka::BOLD,
     28.                                       Czcionka::RED);
     29.      Czcionka tekst(Czcionka::TIMES,  Czcionka::NORMAL,
     30.                                       Czcionka::BLACK);
     31.      Czcionka greka(Czcionka::SYMBOL, Czcionka::BOLD,
     32.                                       Czcionka::BLUE);
     33.      tytul.opis();
     34.      tekst.opis();
     35.      greka.opis();
     36.      cout << "Rozmiar obiektu: " << sizeof(Czcionka) << endl;
     37.  }

Pole przewidziane dla wyboru kroju ma długość trzech bitów, a więc może przyjmować wartości od 0 do 7. Tym wartościom nadaliśmy dla wygody nazwy za pomocą wyliczenia Kroj (). W ten sposób użytkownik klasy nie musi pamiętać, jakie liczby zostały przypisane poszczególnym krojom — może się do nich odnosić poprzez ich czytelne nazwy. Podobnie postąpiliśmy dla wagi (jeden bit, a więc dwie możliwości odpowiadające liczbom 0 i 1) i koloru (dwa bity, a więc cztery możliwości: 0, 1, 2 i 3). Wydruk z tego programu

    Kroj nr 2; Waga nr 1; Kolor nr 1
    Kroj nr 1; Waga nr 0; Kolor nr 0
    Kroj nr 5; Waga nr 1; Kolor nr 3
    Rozmiar obiektu: 4
przekonuje nas, że rozmiar obiektu wynosi cztery bajty, a więc kompilator upakował wszystkie trzy pola w jednym int'cie. Ponieważ kilka pól bitowych może być upakowanych przez kompilator w obszarze jednej liczby całkowitej typu int w sposób trudny do przewidzenia, nie ma sensu stosowanie do składowych bitowych operatora wyłuskania adresu (' &'), a zatem i wskaźników.

Pola takie nie mogą być składowymi statycznymi.

Biblioteka standardowa C++ dostarcza klasę (a właściwie szablon klasy) bitset, która zapewnia funkcjonalność pól bitowych, a zawiera wygodny interfejs dla użytkownika i efektywną implementację. Tam, gdzie to możliwe, należy zatem korzystać z tej klasy, a nie z pól bitowych.

T.R. Werner, 25 lutego 2017; 22:31