środa, 8 września 2010

Operator indeksowania w C++

Czyli praktyczne zastosowanie tego, co już wiemy o const ;)

Załóżmy że potrzebujemy przeciążyć operator indeksowania (czyli []) dla swojej klasy. Weźmy na warsztat prosty przykład, bez żadnych szablonów itd.:

class MyArray
{
public:
  explicit MyArray(const int size) : arr_size(size)
  {
    p_arr = new double[arr_size];
  }

  ~MyArray()
  {
    if (p_arr)
    {
      delete [] p_arr;
    }
  }

  // i także konstruktor kopiujący i operator przypisania ...

private:
  double* p_arr;
  const int arr_size;
};


Chcemy móc korzystać z operatora [] do wyciągania elementów tablicy - dopisujemy więc w części publicznej klasy:

double operator[](const int idx)
{
  if (idx >= 0 && idx < arr_size)
  {
    return p_arr[idx];
  }
  throw std::invalid_argument("idx");
}


Problem pojawia się kiedy chcemy wykorzystać naszą klasę w następującym kodzie:

MyArray m = MyArray(5);
m[0] = 3.14;


Bum, błąd kompilacji. Gdzie jest nasz błąd?

Cóż, zwracana wartość jest tylko lokalną kopią - return niejawnie wywołuje konstruktor kopiujący. Rozwiązaniem jest dodanie jednego ampersandu do nagłówka funkcji:

double& operator[](const int idx)

Super, też już zawsze działa.

Ale zaraz, właśnie napisaliśmy funkcję:

void f(const MyArray& m)
{
  cout << m[0] << endl;
}


I znowu błąd kompilacji - nasz operator [] nie jest metodą const, więc nie może działać na argumencie typu const MyArray&. A przecież nie modyfikujemy naszej klasy, więc na szybciutko dopisujemy do nagłówka:

double& operator[](const int idx) const

Hmmm.... Ale przecież m[0] = 3.14; modyfikuje obiekt m, czyli w sumie to const jest mylące i niepoprawne (uwaga, kompilator to puści!). Jakie jest rozwiązanie? Potrzebujemy przecież i takiej i takiej wersji operatora []...

I tak właśnie napiszemy, dopisując drugą wersję operatora:

double& operator[](const int idx) const
{
  if (idx >= 0 && idx < arr_size)
  {
    return p_arr[idx];
  }
  throw std::invalid_argument("idx");
}


Definicja funkcji jest identyczna, różnica jest tylko w const w nagłówku. OK, zobaczmy czy to zadziała:

void f(const MyArray& m)
{
  m[0] = 3.14;
  cout << m[0] << endl;
}


Nadal nie powoduje błędu kompilacji (a powinno!) - dlaczego? Ponieważ operator [] nawet w wersji const zwraca wynik typu double&. A że nie chcemy mieć możliwości modyfikowania wyniku, to dodajemy kolejne const:

const double& operator[](const int idx) const

I gotowe :) W całości wygląda to tak:

double& operator[](const int idx)
{
  if (idx >= 0 && idx < arr_size)
  {
    return p_arr[idx];
  }
  throw std::invalid_argument("idx");
}

const double& operator[](const int idx) const
{
  if (idx >= 0 && idx < arr_size)
  {
    return p_arr[idx];
  }
  throw std::invalid_argument("idx");
}


Więcej: http://bytes.com/topic/c/answers/654228-index-operator-overloading

Brak komentarzy:

Prześlij komentarz