Sovraccarico operatore [] []


90

È possibile sovraccaricare l' []operatore due volte? Per consentire, qualcosa del genere: function[3][3](come in un array bidimensionale).

Se è possibile, vorrei vedere un codice di esempio.


22
A proposito, è molto più semplice e più comune sovraccaricare operator()(int, int)invece ...
Inverso

2
Perché ricreare la ruota? Basta usare std::vectorcon un costruttore di intervalli: stackoverflow.com/a/25405865/610351
Geoffroy

Oppure puoi semplicemente usare qualcosa comeusing array2d = std::array<std::array<int, 3>, 3>;
adem

Risposte:


118

È possibile eseguire l'overload operator[]per restituire un oggetto su cui è possibile utilizzare operator[]nuovamente per ottenere un risultato.

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;
};

Quindi puoi usarlo come:

ArrayOfArrays aoa;
aoa[3][5];

Questo è solo un semplice esempio, vorresti aggiungere un po 'di controllo dei limiti e cose del genere, ma hai capito.


5
potrebbe usare un distruttore. E Proxy::operator[]dovrebbe tornare int&non soloint
Ryan Haining

1
Meglio usare std::vector<std::vector<int>>per evitare memleak e strani comportamenti sulla copia.
Jarod42

Sia Boost multi_arrayche extent_gensono buoni esempi di questa tecnica. boost.org/doc/libs/1_57_0/libs/multi_array/doc/…
alfC

1
Tuttavia, const ArrayOfArrays arr; arr[3][5] = 42;sarà in grado di passare la compilazione e modifiche arr[3][5], che è in qualche modo diverso da quello che le aspettative degli utenti che arrè const.
abcdabcd987

5
@ abcdabcd987 Non è corretto per un paio di motivi. Innanzitutto, Proxy::operator[]non restituisce un riferimento in questo codice (supponendo che il tuo commento non sia in risposta a Ryan Haining). Ancora più importante, se arrè const, operator[]non può essere utilizzato. Dovresti definire una versione const e ovviamente la faresti tornare const Proxy. Quindi esso Proxystesso avrebbe metodi const e non const. E poi il tuo esempio non si compilerebbe ancora e il programmatore sarebbe felice che tutto vada bene nell'universo. =)
risaia

21

Un'espressione x[y][z]richiede che x[y]restituisca un oggetto dche supporta d[z].

Ciò significa che x[y]dovrebbe essere un oggetto con un operator[]che restituisce un "oggetto proxy" che supporta anche un operator[].

Questo è l'unico modo per incatenarli.

In alternativa, operator()eseguire l' overload per accettare più argomenti, in modo da poter richiamare myObject(x,y).


Perché il sovraccarico delle parentesi consente di ottenere due input ma non è possibile fare lo stesso con le parentesi?
A. Frenzy

19

Per un array bidimensionale, in particolare, potresti farla franca con un singolo overload dell'operatore [] che restituisce un puntatore al primo elemento di ogni riga.

Quindi è possibile utilizzare l'operatore di indicizzazione incorporato per accedere a ogni elemento all'interno della riga.


4
Mi sembra la soluzione più pratica ed efficiente. Mi chiedo perché non ottiene più voti, forse perché non ha il codice accattivante.
Yigal Reiss

16

È possibile se restituisci un qualche tipo di classe proxy nella prima chiamata []. Tuttavia, c'è un'altra opzione: puoi sovraccaricare operator () che può accettare un numero qualsiasi di argomenti ( function(3,3)).


9

Un approccio sta usando 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];
}

Certo, puoi typedefilpair<int,int>


8
Questo diventa molto più interessante con C ++ 11 e l'inizializzazione delle parentesi graffe. Ora puoi scriverenValue = theArray[{2,3}];
Martin Bonner supporta Monica il

5

Puoi usare un oggetto proxy, qualcosa del genere:

#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

Sarebbe fantastico se mi facessi sapere cosa function, function[x]e function[x][y]sono. Ma in ogni caso fammi considerare come un oggetto dichiarato da qualche parte come

SomeClass function;

(Poiché hai detto che è un sovraccarico di operatori, penso che non ti interesserà l'array come SomeClass function[16][32];)

Quindi functionè un'istanza di tipo SomeClass. Quindi cerca la dichiarazione di SomeClassper il tipo restituito di operator[]overload, proprio come

ReturnType operator[](ParamType);

Quindi function[x]avrà il tipo ReturnType. Cerca di nuovo ReturnTypeil operator[]sovraccarico. Se esiste un tale metodo, puoi usare l'espressione function[x][y].

Nota, a differenza function(x, y), function[x][y]sono 2 chiamate separate. Quindi è difficile per il compilatore o il runtime garantisce l'atomicità a meno che non si utilizzi un blocco nel contesto. Un esempio simile è che libc dice che printfè atomico mentre le chiamate successive al operator<<flusso di output sovraccaricato non lo sono. Una dichiarazione come

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

potrebbe avere problemi nell'applicazione multi-thread, ma qualcosa di simile

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

è ok.


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];
};

Ho trovato la mia semplice soluzione a questo.


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)};}

Ciò ti consente di prendere un lambda e produrre un indicizzatore (con []supporto).

Supponi di avere un operator()che supporta il passaggio di entrambe le coordinate su onxe come due argomenti. Ora il [][]supporto per la scrittura è solo:

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);}
  );
}

E fatto. Nessun corso personalizzato richiesto.


2

Se, invece di dire a [x] [y], vuoi dire a [{x, y}], puoi fare così:

struct Coordinate {  int x, y; }

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

1

È possibile eseguire l'overload di più [] utilizzando un gestore di modelli specializzato. Solo per mostrare come funziona:

#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;
}

E ora la definizione di SubscriptHandler<ClassType,ArgType,RetType,N>per far funzionare il codice precedente. Mostra solo come si può fare. Questa soluzione è ottimale né priva di bug (non sicura per i thread, ad esempio).

#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

Con a std::vector<std::vector<type*>>, puoi costruire il vettore interno usando un operatore di input personalizzato che itera sui tuoi dati e restituisce un puntatore a ogni dato.

Per esempio:

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])));
}

Esempio dal vivo

Questa soluzione ha il vantaggio di fornirti un vero contenitore STL, quindi puoi usare speciali per cicli, algoritmi STL e così via.

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

Tuttavia, crea vettori di puntatori, quindi se stai usando piccole strutture dati come questa puoi copiare direttamente il contenuto all'interno dell'array.


0

Codice di esempio:

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>> o T ** è richiesto solo quando hai righe di lunghezza variabile e troppo inefficienti in termini di utilizzo / allocazioni della memoria se hai bisogno di un array rettangolare, considera invece di fare un po 'di matematica! vedi il metodo ():

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

Usando C ++ 11 e la libreria standard puoi creare un array bidimensionale molto carino in una singola riga di codice:

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;

Decidendo che la matrice interna rappresenta le righe, si accede alla matrice con una myMatrix[y][x]sintassi:

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();

E puoi usare a intervalli forper l'output:

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

(Decidere le arraycolonne delle rappresentazioni interne consentirebbe una foo[x][y]sintassi, ma sarebbe necessario utilizzare for(;;)cicli più goffi per visualizzare l'output.)


0

I miei 5 centesimi.

Sapevo intuitivamente che dovevo fare un sacco di codice boilerplate.

Questo è il motivo per cui, invece dell'operatore [], ho eseguito l'overload dell'operatore (int, int). Poi nel risultato finale, invece di m [1] [2], ho fatto m (1,2)

So che è DIVERSA cosa, ma è ancora molto intuitivo e sembra una scrittura matematica.


0

La soluzione più breve e più semplice:

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];
  }

};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.