Przeciążenie operatora [] []


93

Czy można []dwukrotnie przeciążać operatora? Aby pozwolić, coś takiego: function[3][3](jak w dwuwymiarowej tablicy).

Jeśli to możliwe, chciałbym zobaczyć przykładowy kod.


24
Przy okazji, operator()(int, int)zamiast tego przeciążanie jest znacznie prostsze i bardziej powszechne ...
Odwrócenie

2
Po co regenerować koło? Po prostu użyj std::vectorz konstruktorem zakresu: stackoverflow.com/a/25405865/610351
Geoffroy,

Lub możesz po prostu użyć czegoś takiegousing array2d = std::array<std::array<int, 3>, 3>;
adembudak

Odpowiedzi:


121

Możesz przeciążać, operator[]aby zwrócić obiekt, którego możesz użyć operator[]ponownie, aby uzyskać wynik.

class ArrayOfArrays {
public:
    ArrayOfArrays() {
        _arrayofarrays = new int*[10];
        for(int i = 0; i < 10; ++i)
            _arrayofarrays[i] = new int[10];
    }

    class Proxy {
    public:
        Proxy(int* _array) : _array(_array) { }

        int operator[](int index) {
            return _array[index];
        }
    private:
        int* _array;
    };

    Proxy operator[](int index) {
        return Proxy(_arrayofarrays[index]);
    }

private:
    int** _arrayofarrays;
};

Następnie możesz go używać tak:

ArrayOfArrays aoa;
aoa[3][5];

To tylko prosty przykład, chciałbyś dodać kilka sprawdzeń granic i innych rzeczy, ale masz pomysł.


5
przydałby się destruktor. I Proxy::operator[]powinien wrócić int&nie tylkoint
Ryan Haining

1
Lepiej używać, std::vector<std::vector<int>>aby uniknąć memleak i dziwnego zachowania na kopii.
Jarod42

Zarówno Boost, jak multi_arrayi extent_gensą dobrymi przykładami tej techniki. boost.org/doc/libs/1_57_0/libs/multi_array/doc/…
alfC

1
Jednak const ArrayOfArrays arr; arr[3][5] = 42;będą mogli przechodzić kompilację i zmiany arr[3][5], która jest w jakiś sposób różni się od oczekiwań użytkowników, że arrjest const.
abcdabcd987

5
@ abcdabcd987 To nie jest poprawne z kilku powodów. Po pierwsze, Proxy::operator[]nie zwraca odwołania w tym kodzie (zakładając, że Twój komentarz nie jest odpowiedzią dla Ryana Haininga). Co ważniejsze, jeśli arrjest stała, operator[]nie można jej użyć. Musiałbyś zdefiniować wersję const i oczywiście sprawiłbyś, że wróci const Proxy. Wtedy Proxysam miałby metody const i non-const. Twój przykład nadal by się nie kompilował, a programista byłby szczęśliwy, że we wszechświecie wszystko jest w porządku. =)
paddy

21

Wyrażenie x[y][z]wymaga, aby x[y]wartościować do obiektu, dktóry obsługuje d[z].

Oznacza to, że x[y]powinien to być obiekt z atrybutem an, operator[]którego wynikiem jest „obiekt proxy”, który również obsługuje rozszerzenie operator[].

Tylko w ten sposób można je połączyć.

Alternatywnie możesz przeciążać, operator()aby pobrać wiele argumentów, tak aby można było wywołać myObject(x,y).


Dlaczego przeciążenie nawiasami umożliwia uzyskanie dwóch danych wejściowych, ale nie można zrobić tego samego z nawiasami?
A. Frenzy

20

W szczególności w przypadku tablic dwuwymiarowych można uniknąć przeciążenia pojedynczego operatora [], które zwraca wskaźnik do pierwszego elementu każdego wiersza.

Następnie możesz użyć wbudowanego operatora indeksowania, aby uzyskać dostęp do każdego elementu w wierszu.


4
Wydaje mi się, że jest to najbardziej praktyczne i wydajne rozwiązanie. Ciekawe, dlaczego nie ma więcej głosów - może dlatego, że nie ma przyciągającego wzrok kodu.
Yigal Reiss

16

Jest to możliwe, jeśli w pierwszym wywołaniu [] zwrócisz jakąś klasę proxy. Jest jednak inna opcja: możesz przeciążyć operator (), który może przyjąć dowolną liczbę argumentów ( function(3,3)).


10

Jedną z metod jest użycie std::pair<int,int>:

class Array2D
{
    int** m_p2dArray;
public:
    int operator[](const std::pair<int,int>& Index)
    {
       return m_p2dArray[Index.first][Index.second];
    }
};

int main()
{
    Array2D theArray;
    pair<int, int> theIndex(2,3);
    int nValue;
    nValue = theArray[theIndex];
}

Oczywiście, możesztypedefpair<int,int>


9
Staje się to o wiele bardziej atrakcyjne dzięki C ++ 11 i inicjalizacji nawiasów klamrowych. Teraz możesz pisaćnValue = theArray[{2,3}];
Martin Bonner wspiera Monikę

5

Możesz użyć obiektu proxy, czegoś takiego:

#include <iostream>

struct Object
{
    struct Proxy
    {
        Object *mObj;
        int mI;

        Proxy(Object *obj, int i)
        : mObj(obj), mI(i)
        {
        }

        int operator[](int j)
        {
            return mI * j;
        }
    };

    Proxy operator[](int i)
    {
        return Proxy(this, i);
    }
};

int main()
{
    Object o;
    std::cout << o[2][3] << std::endl;
}

4

To „ll być wielki, jeśli możesz dać mi znać, co function, function[x]i function[x][y]są. W każdym razie pozwólcie, że uznam to za obiekt zadeklarowany gdzieś w podobny sposób

SomeClass function;

(Ponieważ powiedziałeś, że to przeciążenie operatorów, myślę, że nie będziesz zainteresowany tablicami takimi jak SomeClass function[16][32];)

Tak functionjest w przypadku typu SomeClass. Następnie wyszukaj deklarację SomeClasszwracanego typu operator[]przeciążenia, tak jak

ReturnType operator[](ParamType);

Wtedy function[x]będzie miał typ ReturnType. Ponownie spojrzeć ReturnTypena operator[]przeciążenia. Jeśli istnieje taka metoda, możesz użyć wyrażenia function[x][y].

Uwaga, w odróżnieniu function(x, y), function[x][y]są 2 oddzielne rozmowy. Tak więc jest to trudne dla kompilatora lub środowiska wykonawczego, które gwarantuje atomowość, chyba że użyjesz blokady w kontekście. Podobny przykład jest taki, że libc mówi, że printfjest atomowy, podczas gdy kolejne wywołania przeciążonego operator<<strumienia wyjściowego nie. Oświadczenie jak

std::cout << "hello" << std::endl;

może mieć problem w aplikacji wielowątkowej, ale coś w rodzaju

printf("%s%s", "hello", "\n");

jest w porządku.


2
#include<iostream>

using namespace std;

class Array 
{
     private: int *p;
     public:
          int length;
          Array(int size = 0): length(size)
          {
                p=new int(length);
          }
          int& operator [](const int k)
          {
               return p[k];
          }
};
class Matrix
{
      private: Array *p;
      public: 
            int r,c;
            Matrix(int i=0, int j=0):r(i), c(j)
            {
                 p= new Array[r];
            }
            Array& operator [](const int& i)
            {
                 return p[i];
            }
};

/*Driver program*/
int main()
{
    Matrix M1(3,3); /*for checking purpose*/
    M1[2][2]=5;
}

2
struct test
{
    using array_reference = int(&)[32][32];

    array_reference operator [] (std::size_t index)
    {
        return m_data[index];
    }

private:

    int m_data[32][32][32];
};

Znalazłem własne proste rozwiązanie tego problemu.


2
template<class F>
struct indexer_t{
  F f;
  template<class I>
  std::result_of_t<F const&(I)> operator[](I&&i)const{
    return f(std::forward<I>(i))1;
  }
};
template<class F>
indexer_t<std::decay_t<F>> as_indexer(F&& f){return {std::forward<F>(f)};}

Dzięki temu możesz wziąć lambdę i utworzyć indeksator (z []obsługą).

Załóżmy, że masz operator()argument, który obsługuje przekazywanie obu współrzędnych w onxe jako dwóch argumentów. Teraz pisanie [][]wsparcia to po prostu:

auto operator[](size_t i){
  return as_indexer(
    [i,this](size_t j)->decltype(auto)
    {return (*this)(i,j);}
  );
}

auto operator[](size_t i)const{
  return as_indexer(
    [i,this](size_t j)->decltype(auto)
    {return (*this)(i,j);}
  );
}

I zrobione. Nie jest wymagana żadna klasa niestandardowa.


2

Jeśli zamiast mówić a [x] [y], chcesz powiedzieć [{x, y}], możesz zrobić to w ten sposób:

struct Coordinate {  int x, y; }

class Matrix {
    int** data;
    operator[](Coordinate c) {
        return data[c.y][c.x];
    }
}

1

Możliwe jest przeciążenie wielu [] za pomocą wyspecjalizowanego programu obsługi szablonów. Aby pokazać, jak to działa:

#include <iostream>
#include <algorithm>
#include <numeric>
#include <tuple>
#include <array>

using namespace std;

// the number '3' is the number of [] to overload (fixed at compile time)
struct TestClass : public SubscriptHandler<TestClass,int,int,3> {

    // the arguments will be packed in reverse order into a std::array of size 3
    // and the last [] will forward them to callSubscript()
    int callSubscript(array<int,3>& v) {
        return accumulate(v.begin(),v.end(),0);
    }

};

int main() {


    TestClass a;
    cout<<a[3][2][9];  // prints 14 (3+2+9)

    return 0;
}

A teraz definicja, SubscriptHandler<ClassType,ArgType,RetType,N>aby poprzedni kod działał. Pokazuje tylko, jak można to zrobić. To rozwiązanie jest optymalne i wolne od błędów (na przykład nie jest bezpieczne dla wątków).

#include <iostream>
#include <algorithm>
#include <numeric>
#include <tuple>
#include <array>

using namespace std;

template <typename ClassType,typename ArgType,typename RetType, int N> class SubscriptHandler;

template<typename ClassType,typename ArgType,typename RetType, int N,int Recursion> class SubscriptHandler_ {

    ClassType*obj;
    array<ArgType,N+1> *arr;

    typedef SubscriptHandler_<ClassType,ArgType,RetType,N,Recursion-1> Subtype;

    friend class SubscriptHandler_<ClassType,ArgType,RetType,N,Recursion+1>;
    friend class SubscriptHandler<ClassType,ArgType,RetType,N+1>;

public:

    Subtype operator[](const ArgType& arg){
        Subtype s;
        s.obj = obj;
        s.arr = arr;
        arr->at(Recursion)=arg;
        return s;
    }
};

template<typename ClassType,typename ArgType,typename RetType,int N> class SubscriptHandler_<ClassType,ArgType,RetType,N,0> {

    ClassType*obj;
    array<ArgType,N+1> *arr;

    friend class SubscriptHandler_<ClassType,ArgType,RetType,N,1>;
    friend class SubscriptHandler<ClassType,ArgType,RetType,N+1>;

public:

    RetType operator[](const ArgType& arg){
        arr->at(0) = arg;
        return obj->callSubscript(*arr);
    }

};


template<typename ClassType,typename ArgType,typename RetType, int N> class SubscriptHandler{

    array<ArgType,N> arr;
    ClassType*ptr;
    typedef SubscriptHandler_<ClassType,ArgType,RetType,N-1,N-2> Subtype;

protected:

    SubscriptHandler() {
        ptr=(ClassType*)this;
    }

public:

    Subtype operator[](const ArgType& arg){
        Subtype s;
        s.arr=&arr;
        s.obj=ptr;
        s.arr->at(N-1)=arg;
        return s;
    }
};

template<typename ClassType,typename ArgType,typename RetType> struct SubscriptHandler<ClassType,ArgType,RetType,1>{
    RetType operator[](const ArgType&arg) {
        array<ArgType,1> arr;
        arr.at(0)=arg;
        return ((ClassType*)this)->callSubscript(arr);
    }
};

0

Za pomocą a std::vector<std::vector<type*>>można zbudować wektor wewnętrzny przy użyciu niestandardowego operatora wejściowego, który iteruje po danych i zwraca wskaźnik do każdej z nich.

Na przykład:

size_t w, h;
int* myData = retrieveData(&w, &h);

std::vector<std::vector<int*> > data;
data.reserve(w);

template<typename T>
struct myIterator : public std::iterator<std::input_iterator_tag, T*>
{
    myIterator(T* data) :
      _data(data)
    {}
    T* _data;

    bool operator==(const myIterator& rhs){return rhs.data == data;}
    bool operator!=(const myIterator& rhs){return rhs.data != data;}
    T* operator*(){return data;}
    T* operator->(){return data;}

    myIterator& operator++(){data = &data[1]; return *this; }
};

for (size_t i = 0; i < w; ++i)
{
    data.push_back(std::vector<int*>(myIterator<int>(&myData[i * h]),
        myIterator<int>(&myData[(i + 1) * h])));
}

Przykład na żywo

To rozwiązanie ma tę zaletę, że zapewnia prawdziwy kontener STL, więc możesz użyć specjalnego dla pętli, algorytmów STL i tak dalej.

for (size_t i = 0; i < w; ++i)
  for (size_t j = 0; j < h; ++j)
    std::cout << *data[i][j] << std::endl;

Jednak tworzy wektory wskaźników, więc jeśli używasz małych struktur danych, takich jak ta, możesz bezpośrednio skopiować zawartość wewnątrz tablicy.


0

Przykładowy kod:

template<class T>
class Array2D
{
public:
    Array2D(int a, int b)  
    {
        num1 = (T**)new int [a*sizeof(int*)];
        for(int i = 0; i < a; i++)
            num1[i] = new int [b*sizeof(int)];

        for (int i = 0; i < a; i++) {
            for (int j = 0; j < b; j++) {
                num1[i][j] = i*j;
            }
        }
    }
    class Array1D
    {
    public:
        Array1D(int* a):temp(a) {}
        T& operator[](int a)
        {
            return temp[a];
        }
        T* temp;
    };

    T** num1;
    Array1D operator[] (int a)
    {
        return Array1D(num1[a]);
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    Array2D<int> arr(20, 30);

    std::cout << arr[2][3];
    getchar();
    return 0;
}

0

vector <vector <T>> lub T ** jest wymagane tylko wtedy, gdy masz wiersze o zmiennej długości i są one zbyt nieefektywne pod względem wykorzystania / alokacji pamięci, jeśli potrzebujesz tablicy prostokątnej, rozważ zamiast tego trochę matematyki! patrz metoda ():

template<typename T > class array2d {

protected:
    std::vector< T > _dataStore;
    size_t _sx;

public:
    array2d(size_t sx, size_t sy = 1): _sx(sx), _dataStore(sx*sy) {}
    T& at( size_t x, size_t y ) { return _dataStore[ x+y*sx]; }
    const T& at( size_t x, size_t y ) const { return _dataStore[ x+y*sx]; }
    const T& get( size_t x, size_t y ) const { return at(x,y); }
    void set( size_t x, size_t y, const T& newValue ) { at(x,y) = newValue; }
};

0

Używając C ++ 11 i Biblioteki Standardowej, możesz stworzyć bardzo ładną dwuwymiarową tablicę w jednej linii kodu:

std::array<std::array<int, columnCount>, rowCount> myMatrix {0};

std::array<std::array<std::string, columnCount>, rowCount> myStringMatrix;

std::array<std::array<Widget, columnCount>, rowCount> myWidgetMatrix;

Decydując, że wewnętrzna macierz reprezentuje wiersze, uzyskujesz dostęp do macierzy za pomocą myMatrix[y][x]składni:

myMatrix[0][0] = 1;
myMatrix[0][3] = 2;
myMatrix[3][4] = 3;

std::cout << myMatrix[3][4]; // outputs 3

myStringMatrix[2][4] = "foo";
myWidgetMatrix[1][5].doTheStuff();

I możesz użyć ranged- fordo wyjścia:

for (const auto &row : myMatrix) {
  for (const auto &elem : row) {
    std::cout << elem << " ";
  }
  std::cout << std::endl;
}

(Zdecydowanie, że wewnętrzna arrayreprezentuje kolumny pozwoliłaby na foo[x][y]składnię, ale for(;;)do wyświetlenia danych wyjściowych konieczne byłoby użycie bardziej niezgrabnych pętli).


0

Moje 5 centów.

Intuicyjnie wiedziałem, że muszę zrobić dużo standardowego kodu.

Dlatego zamiast operatora [] zrobiłem przeciążony operator (int, int). Wtedy w ostatecznym wyniku zamiast m [1] [2] zrobiłem m (1,2)

Wiem, że to INNA rzecz, ale nadal jest bardzo intuicyjny i wygląda jak skrypt matematyczny.


0

Najkrótsze i najłatwiejsze rozwiązanie:

class Matrix
{
public:
  float m_matrix[4][4];

// for statements like matrix[0][0] = 1;
  float* operator [] (int index) 
  {
    return m_matrix[index];
  }

// for statements like matrix[0][0] = otherMatrix[0][0];
  const float* operator [] (int index) const 
  {
    return m_matrix[index];
  }

};
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.