Перевантаження оператора [] []


93

Чи можна перевантажити []оператора двічі? Щоб дозволити, щось приблизно таке: function[3][3](як у двовимірному масиві).

Якщо це можливо, я хотів би побачити приклад коду.


23
До речі, набагато простіше і частіше перевантажувати operator()(int, int)замість цього ...
Інверсія

2
Навіщо відтворювати колесо? Просто використовуйте std::vectorз конструктором діапазону: stackoverflow.com/a/25405865/610351
Geoffroy

Або ви можете просто використати щось на кшталт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

5
@ abcdabcd987 Це неправильно з кількох причин. По-перше, Proxy::operator[]не повертає посилання в цьому коді (якщо ваш коментар не відповідає Райану Хейнінгу). Що ще важливіше, якщо arrце const, тоді operator[]його не можна використовувати. Вам довелося б визначити const-версію, і ви, звичайно, змусили б її повернути const Proxy. Тоді Proxyб сам мав методи const та non-const. І тоді ваш приклад все одно не компілюється, і програміст буде радий, що у Всесвіті все гаразд і добре. =)
педді

21

Вираз x[y][z]вимагає x[y]оцінки об'єкта, dякий підтримує d[z].

Це означає, що це x[y]повинен бути об'єкт, operator[]який має значення "проксі-об'єкт", який також підтримує operator[].

Тільки так їх можна прив’язати.

Або перевантажте, operator()щоб взяти кілька аргументів, таких, які ви можете викликати myObject(x,y).


Чому перевантаження дужок дозволяє отримати два входи, але ви не можете зробити те саме з дужками?
А. Шаленість

20

Зокрема, для двовимірного масиву ви можете уникнути перевантаження одного оператора [], який повертає покажчик на перший елемент кожного рядка.

Тоді ви можете використовувати вбудований оператор індексування для доступу до кожного елемента в рядку.


4
Мені здається найбільш практичним та ефективним рішенням. Цікаво, чому він не отримує більше голосів - можливо, тому, що він не має привабливого коду.
Yigal Reiss

16

Це можливо, якщо ви повернете якийсь клас проксі в першому [] виклику. Однак є інший варіант: ви можете перевантажити оператор (), який може прийняти будь-яку кількість аргументів ( function(3,3)).


9

Один із підходів - використання 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>


8
Це стає набагато привабливішим із 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, тому ви можете використовувати спеціальні для циклів, алгоритми 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 ** потрібен лише тоді, коли у вас є рядки змінної довжини і занадто неефективні з точки зору використання / розподілу пам'яті, якщо вам потрібен прямокутний масив, подумайте про те, щоб замість цього зробити математику! див. метод ():

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 центів.

Я інтуїтивно знав, що мені потрібно зробити багато зразкового коду.

Ось чому замість оператора [] я зробив перевантажений оператор (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.