Überlastung des Bedieners [] []

90

Ist es möglich, den []Bediener zweimal zu überlasten ? Um dies zu ermöglichen: function[3][3](wie in einem zweidimensionalen Array).

Wenn es möglich ist, würde ich gerne einen Beispielcode sehen.

Eispopo
quelle
22
Übrigens ist es viel einfacher und häufiger, operator()(int, int)stattdessen zu überladen ...
Inverse
2
Warum das Rad neu erstellen? Verwenden Sie einfach std::vectormit einem Bereichskonstruktor: stackoverflow.com/a/25405865/610351
Geoffroy
Oder Sie können einfach so etwas wieusing array2d = std::array<std::array<int, 3>, 3>;
adem

Antworten:

118

Sie können überladen operator[], um ein Objekt zurückzugeben, mit dem Sie operator[]erneut ein Ergebnis erhalten können.

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

Dann können Sie es wie folgt verwenden:

ArrayOfArrays aoa;
aoa[3][5];

Dies ist nur ein einfaches Beispiel. Sie möchten eine Reihe von Grenzprüfungen und anderen Dingen hinzufügen, aber Sie haben die Idee.

Seth Carnegie
quelle
5
könnte einen Destruktor verwenden. Und Proxy::operator[]sollte int&nicht nur zurückkehrenint
Ryan Haining
1
Besser zu verwenden std::vector<std::vector<int>>, um Memleak und seltsames Verhalten beim Kopieren zu vermeiden.
Jarod42
Sowohl Boost's multi_arrayals auch extent_gensind gute Beispiele für diese Technik. boost.org/doc/libs/1_57_0/libs/multi_array/doc/…
alfC
1
Allerdings const ArrayOfArrays arr; arr[3][5] = 42;werden in der Lage Zusammenstellung und Veränderungen passieren arr[3][5], die von dem, was der Benutzer Erwartung irgendwie anders ist , das arrist const.
abcdabcd987
5
@ abcdabcd987 Das ist aus mehreren Gründen nicht korrekt. Gibt zunächst Proxy::operator[]keinen Verweis in diesem Code zurück (vorausgesetzt, Ihr Kommentar ist keine Antwort auf Ryan Haining). Noch wichtiger ist, wenn arris const ist, operator[]kann es nicht verwendet werden. Sie müssten eine const-Version definieren und sie natürlich zurückgeben const Proxy. Dann Proxyselbst hätte const und non-const Methoden. Und dann würde Ihr Beispiel immer noch nicht kompiliert, und der Programmierer würde sich freuen, dass im Universum alles in Ordnung und gut ist. =)
Paddy
21

Ein Ausdruck x[y][z]erfordert, dass er x[y]zu einem dunterstützten Objekt ausgewertet wird d[z].

Dies bedeutet , dass x[y]ein Objekt mit einem sein sollte , operator[]daß auswertet auf ein „Proxy - Objekt“ , das auch eine unterstützt operator[].

Nur so können sie verkettet werden.

Alternativ können Sie überladen operator(), um mehrere Argumente zu verwenden, die Sie möglicherweise aufrufen myObject(x,y).

Leichtigkeitsrennen im Orbit
quelle
Warum ermöglicht die Überladung von Klammern das Abrufen von zwei Eingaben, aber Sie können nicht dasselbe mit den Klammern tun?
A. Frenzy
19

Insbesondere bei einem zweidimensionalen Array kann es vorkommen, dass Sie eine einzelne Operator [] -Überladung haben, die einen Zeiger auf das erste Element jeder Zeile zurückgibt.

Anschließend können Sie mit dem integrierten Indizierungsoperator auf jedes Element in der Zeile zugreifen.

Bo Persson
quelle
4
Sieht für mich nach der praktischsten und effizientesten Lösung aus. Ich frage mich, warum es nicht mehr Stimmen bekommt - vielleicht weil es nicht den auffälligen Code hat.
Yigal Reiss
16

Es ist möglich, wenn Sie beim ersten [] Aufruf eine Art Proxy-Klasse zurückgeben. Es gibt jedoch eine andere Option: Sie können operator () überladen, das eine beliebige Anzahl von Argumenten ( function(3,3)) akzeptieren kann .

John
quelle
8

Ein Ansatz ist 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];
}

Natürlich darfst du typedefdaspair<int,int>

Ajay
quelle
7
Dies wird mit C ++ 11 und der Klammerinitialisierung viel attraktiver. Jetzt können Sie schreibennValue = theArray[{2,3}];
Martin Bonner unterstützt Monica
5

Sie können ein Proxy-Objekt verwenden, etwa so:

#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;
}
Knoten
quelle
4

Es würde groß sein , wenn Sie mich , was sie wissen function, function[x]und function[x][y]sind. Aber lassen Sie mich es trotzdem als ein Objekt betrachten, das irgendwo wie deklariert wurde

SomeClass function;

(Da Sie sagten, dass es sich um eine Überlastung des Operators handelt, werden Sie sich wahrscheinlich nicht für Array-ähnliche interessieren. SomeClass function[16][32];)

So functionist eine Instanz des Typs SomeClass. Suchen Sie dann nach der Deklaration SomeClassfür den Rückgabetyp der operator[]Überladung, genau wie

ReturnType operator[](ParamType);

Dann function[x]wird der Typ haben ReturnType. Suchen Sie erneut nach ReturnTypeder operator[]Überlastung. Wenn es eine solche Methode gibt, können Sie den Ausdruck verwenden function[x][y].

Beachten Sie , im Gegensatz zu function(x, y), function[x][y]sind 2 separate Anrufe. Daher ist es für Compiler oder Laufzeit schwierig, die Atomizität zu gewährleisten, es sei denn, Sie verwenden eine Sperre im Kontext. Ein ähnliches Beispiel ist, libc sagt, printfist atomar, während sukzessive Aufrufe an den überlasteten operator<<Ausgabestream nicht sind. Eine Aussage wie

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

Möglicherweise gibt es ein Problem mit Multithread-Anwendungen, aber so etwas wie

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

ist gut.

Neuront
quelle
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;
}
Kaustav Ray
quelle
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];
};

Ich habe meine eigene einfache Lösung dafür gefunden.

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

Auf diese Weise können Sie ein Lambda nehmen und einen Indexer (mit []Unterstützung) erstellen .

Angenommen, Sie haben eine operator(), die die Übergabe beider Koordinaten an onxe als zwei Argumente unterstützt. Jetzt ist das Schreiben von [][]Unterstützung nur noch:

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

Und fertig. Keine benutzerdefinierte Klasse erforderlich.

Yakk - Adam Nevraumont
quelle
2

Wenn Sie anstelle von [x] [y] ein [{x, y}] sagen möchten, können Sie Folgendes tun:

struct Coordinate {  int x, y; }

class Matrix {
    int** data;
    operator[](Coordinate c) {
        return data[c.y][c.x];
    }
}
Erel Segal-Halevi
quelle
1

Es ist möglich, mehrere [] mit einem speziellen Vorlagenhandler zu überladen. Nur um zu zeigen, wie es funktioniert:

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

Und jetzt die Definition SubscriptHandler<ClassType,ArgType,RetType,N>, damit der vorherige Code funktioniert. Es zeigt nur, wie es gemacht werden kann. Diese Lösung ist weder optimal noch fehlerfrei (zum Beispiel nicht threadsicher).

#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);
    }
};
Frédéric Terrazzoni
quelle
0

Mit einer std::vector<std::vector<type*>> können Sie den inneren Vektor mithilfe eines benutzerdefinierten Eingabeoperators erstellen, der Ihre Daten durchläuft und einen Zeiger auf die einzelnen Daten zurückgibt.

Beispielsweise:

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

Live Beispiel

Diese Lösung bietet den Vorteil, dass Sie einen echten STL-Container erhalten, sodass Sie spezielle Funktionen für Schleifen, STL-Algorithmen usw. verwenden können.

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

Es werden jedoch Vektoren von Zeigern erstellt. Wenn Sie also kleine Datenstrukturen wie diese verwenden, können Sie den Inhalt direkt in das Array kopieren.

Geoffroy
quelle
0

Beispielcode:

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;
}
Anant Rai
quelle
0

Vektor <Vektor <T>> oder T ** ist nur erforderlich, wenn Sie Zeilen mit variabler Länge und viel zu ineffizienter Speichernutzung / Zuweisung haben, wenn Sie ein rechteckiges Array benötigen. siehe at () Methode:

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; }
};
xakepp35
quelle
0

Mit C ++ 11 und der Standardbibliothek können Sie ein sehr schönes zweidimensionales Array in einer einzigen Codezeile erstellen:

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;

Indem Sie entscheiden, dass die innere Matrix Zeilen darstellt, greifen Sie mit einer myMatrix[y][x]Syntax auf die Matrix zu :

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

Und Sie können Fernkampf forfür die Ausgabe verwenden:

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

(Die Entscheidung, ob die inneren arraySpalten Spalten darstellen, würde eine foo[x][y]Syntax ermöglichen, aber Sie müssten umständlichere for(;;)Schleifen verwenden, um die Ausgabe anzuzeigen.)

Jack Deeth
quelle
0

Meine 5 Cent.

Ich wusste intuitiv, dass ich viel Boilerplate-Code machen muss.

Aus diesem Grund habe ich anstelle von operator [] den Operator (int, int) überladen. Dann habe ich im Endergebnis anstelle von m [1] [2] m (1,2)

Ich weiß, dass es eine andere Sache ist, aber es ist immer noch sehr intuitiv und sieht aus wie eine mathematische Schrift.

Nick
quelle
0

Die kürzeste und einfachste Lösung:

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

};
Vegeta
quelle