Оператор [] [] перегрузка


93

Можно ли []дважды перегрузить оператора? Чтобы разрешить, что-то вроде этого: function[3][3](как в двумерном массиве).

Если возможно, я хотел бы увидеть пример кода.


24
Между прочим, гораздо проще и чаще использовать перегрузку operator()(int, int)...
Обратный

2
Зачем воссоздавать колесо? Просто используйте std::vectorс конструктором диапазона: stackoverflow.com/a/25405865/610351
Джеффрой

Или вы можете просто использовать что-то вродеusing array2d = std::array<std::array<int, 3>, 3>;
adembudak

Ответы:


121

Вы можете перегрузить, operator[]чтобы вернуть объект, который можно operator[]снова использовать для получения результата.

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

Тогда вы можете использовать это как:

ArrayOfArrays aoa;
aoa[3][5];

Это всего лишь простой пример, вы бы хотели добавить кучу проверки границ и прочего, но вы поняли идею.


5
может использовать деструктор. И Proxy::operator[]должен вернуться int&не просто такint
Райан Хайнинг

1
Лучше использовать, std::vector<std::vector<int>>чтобы избежать утечки памяти и странного поведения при копировании.
Jarod42

Оба Boost multi_arrayи extent_genявляются хорошими примерами этой техники. boost.org/doc/libs/1_57_0/libs/multi_array/doc/…
alfC

1
Тем не менее, const ArrayOfArrays arr; arr[3][5] = 42;будет иметь возможность пройти компиляцию и изменения arr[3][5], которые каким - то образом отличается от того , ожидания пользователей , что arrесть const.
abcdabcd987 08

5
@ abcdabcd987 Это неверно по нескольким причинам. Во-первых, Proxy::operator[]не возвращает ссылку в этом коде (при условии, что ваш комментарий не является ответом Райану Хейнингу). Что еще более важно, если arrconst, то operator[]использовать нельзя. Вам нужно будет определить константную версию и, конечно, вы должны вернуть ее const Proxy. Тогда у Proxyсебя были бы методы const и неконстантные. И тогда ваш пример все равно не компилируется, и программист будет счастлив, что во вселенной все хорошо. =)
падди

21

Выражение x[y][z]требует, чтобы результат x[y]оценивался как dподдерживающий объект d[z].

Это означает, что это x[y]должен быть объект с расширением, operator[]который оценивается как «прокси-объект», который также поддерживает operator[].

Это единственный способ связать их.

В качестве альтернативы перегрузите, operator()чтобы принять несколько аргументов, чтобы вы могли вызвать myObject(x,y).


Почему перегрузка скобок позволяет получить два ввода, но вы не можете сделать то же самое со скобками?
A. Frenzy

20

В частности, для двумерного массива можно обойтись одной перегрузкой operator [], которая возвращает указатель на первый элемент каждой строки.

Затем вы можете использовать встроенный оператор индексации для доступа к каждому элементу в строке.


4
Мне кажется наиболее практичным и эффективным решением. Интересно, почему он не набирает больше голосов - может быть, потому, что у него нет привлекательного кода.
Игаль Рейсс

16

Это возможно, если вы вернете какой-то прокси-класс в вызове first []. Однако есть и другой вариант: вы можете перегрузить operator (), который может принимать любое количество аргументов ( function(3,3)).


10

Один из подходов использует 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];
}

Конечно, вы можетеtypedefpair<int,int>


9
Это становится намного более привлекательным с C ++ 11 и инициализацией скобок. Теперь вы можете писатьnValue = theArray[{2,3}];
Мартин Боннер поддерживает Монику

5

Вы можете использовать прокси-объект, например:

#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

Это будет замечательно , если вы можете , дайте мне знать , что function, function[x]и function[x][y]есть. Но в любом случае позвольте мне рассматривать это как объект, объявленный где-то вроде

SomeClass function;

(Поскольку вы сказали, что это перегрузка оператора, я думаю, вам не будет интересен массив вроде SomeClass function[16][32];)

Так functionчто это экземпляр типа SomeClass. Затем найдите объявление SomeClassдля возвращаемого типа operator[]перегрузки, как и

ReturnType operator[](ParamType);

Тогда function[x]будет тип ReturnType. Опять же искать ReturnTypeдля operator[]перегрузки. Если есть такой метод, вы можете использовать выражение function[x][y].

Обратите внимание, в отличие от этого function(x, y), function[x][y]это 2 отдельных вызова. Таким образом, компилятору или среде выполнения сложно гарантировать атомарность, если вы не используете блокировку в контексте. Похожий пример: libc утверждает, что printfэто атомарно, а последовательные вызовы перегруженного operator<<выходного потока - нет. Заявление вроде

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

может быть проблема в многопоточном приложении, но что-то вроде

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

Это хорошо.


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

Нашел собственное простое решение этой проблемы.


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

Это позволяет вам взять лямбду и создать индексатор (с []поддержкой).

Предположим, у вас есть объект, operator()который поддерживает передачу обеих координат в onxe в качестве двух аргументов. Теперь написание [][]поддержки - это просто:

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

И готово. Не требует специального класса.


2

Если вместо [x] [y] вы хотите сказать [{x, y}], вы можете сделать следующее:

struct Coordinate {  int x, y; }

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

1

Можно перегрузить несколько [], используя специализированный обработчик шаблонов. Просто чтобы показать, как это работает:

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

А теперь определение, SubscriptHandler<ClassType,ArgType,RetType,N>как заставить работать предыдущий код. Это только показывает, как это можно сделать. Это решение является оптимальным и не лишенным ошибок (например, небезопасным для потоков).

#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

С помощью a std::vector<std::vector<type*>>вы можете построить внутренний вектор, используя пользовательский оператор ввода, который выполняет итерацию по вашим данным и возвращает указатель на все данные.

Например:

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

Живой пример

Это решение имеет то преимущество, что предоставляет вам настоящий контейнер STL, поэтому вы можете использовать специальные циклы for, алгоритмы STL и так далее.

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

Однако он создает векторы указателей, поэтому, если вы используете небольшие структуры данных, такие как эта, вы можете напрямую копировать содержимое внутри массива.


0

Образец кода:

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>> или T ** требуется только тогда, когда у вас есть строки переменной длины и они слишком неэффективны с точки зрения использования / выделения памяти, если вам нужен прямоугольный массив, подумайте о том, чтобы вместо этого выполнить некоторую математику! см. метод at ():

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

Используя C ++ 11 и Стандартную библиотеку, вы можете создать очень красивый двумерный массив в одной строке кода:

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;

Решив, что внутренняя матрица представляет строки, вы получаете доступ к матрице с помощью myMatrix[y][x]синтаксиса:

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

И вы можете использовать ранжированный forдля вывода:

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

(Решение о arrayстолбцах внутреннего представления позволило бы использовать foo[x][y]синтаксис, но for(;;)для отображения вывода нужно использовать более неуклюжие циклы.)


0

Мои 5 центов.

Я интуитивно знал, что мне нужно сделать много шаблонного кода.

Вот почему вместо operator [] я использовал перегруженный оператор (int, int). Тогда в конечном результате вместо m [1] [2] я сделал m (1,2)

Я знаю, что это ДРУГАЯ вещь, но все еще очень интуитивно понятна и выглядит как математический сценарий.


0

Самое короткое и простое решение:

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

};
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.