Как определить оператор Double Brackets/Double Iterator, аналогичный вектору векторов?

Я переношу код, который использует очень большой массив float, что может привести к сбоям malloc с c до С++. Я задал вопрос о том, следует ли использовать векторы или знаки и Niki Yoshiuchi щедро предложил мне этот пример безопасного типа:

template<typename T>
class VectorDeque
{
private:
  enum TYPE { NONE, DEQUE, VECTOR };
  std::deque<T> m_d;
  std::vector<T> m_v;
  TYPE m_type;
  ...
public:
  void resize(size_t n)
  {
    switch(m_type)
    {
      case NONE:
      try
      {
        m_v.resize(n);
        m_type = VECTOR;
      }
      catch(std::bad_alloc &ba)
      {
        m_d.resize(n);
        m_type = DEQUE;
      }
      break;
    }
  }
};

Мне нужен 2D-вектор векторов /deque из deques, поэтому я изменил его на следующий код:

template<typename T>
class VectorDeque
{
private:
  enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR };
  std::deque<std::deque<T> > x_d,y_d,z_d;
  std::vector<std::vector<T> > x_v,y_v,z_v;
  TYPE my_container;
public:
  void resize(size_t num_atoms, size_t num_frames)
  {
    switch(m_type)
    {
      case NONE:
      try
      {
        x_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   x_v[counter].resize(num_frames);
        y_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   y_v[counter].resize(num_frames);
        z_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   z_v[counter].resize(num_frames);
        my_container = VECTOR;
      }
      catch(std::bad_alloc &e)
      {
        x_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   x_d[counter].resize(num_frames);
        y_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   y_d[counter].resize(num_frames);
        z_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   z_d[counter].resize(num_frames);
        my_container = DEQUE;
      }
      break;
    }
  }
};

Теперь я хочу иметь возможность определять мои операторы скобок, чтобы я мог иметь инструкцию типа x[1][2] получить прямой доступ в зависимости от того, какой я использую реальный контейнер памяти (заданный значением моей перечислимой переменной.

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

Как вы можете перегрузить двойные скобки?

Кроме того, как бы вы перегружали двойные итераторы (в случае, если я хочу использовать итератор, в отличие от прямой индексации)?

ИЗМЕНИТЬ 1:

Основываясь на решении от Martin York/Matteo Italia, я разработал следующий класс:

template<typename T>
class VectorDeque2D
{
public:

  class VectorDeque2D_Inner_Set
  {
    VectorDeque2D& parent;
    int   first_index;
  public:
    // Just init the temp object
    VectorDeque2D_Inner_Set(My2D& p, int first_Index) : 
      parent(p), 
      first_Index(first_index) {} 
    // Here we get the value.
    T& operator[](int second_index)  const 
    { return parent.get(first_index,second_index);}   
  };

  // Return an object that defines its own operator[] that will access the data.
  // The temp object is very trivial and just allows access to the data via 
  // operator[]
  VectorDeque2D_Inner_Set operator[](unsigned int first_index) { 
    return (*this, x);
  }


  void resize_first_index(unsigned int first_index) {
    try {
      my_vector.resize(first_index);
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      my_deque.resize(first_index);
      my_container = DEQUE;
    }
  }

  void resize_second_index(unsigned int second_index) {
    try {
      for (unsigned int couter=0;couter < my_vector.size(); counter++) {
    my_vector[counter].resize(second_index);
      }
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      for (unsigned int couter=0;couter < my_deque.size(); counter++) {
    my_deque[counter].resize(second_index);
      }
      my_container = DEQUE;
    }
  }
  void resize(unsigned int first_index,
          unsigned int second_index) {
    try {
      my_vector.resize(first_index);
      for (unsigned int couter=0;couter < my_vector.size(); counter++) {
    my_vector[counter].resize(second_index);
      }
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      my_deque.resize(first_index);
      for (unsigned int couter=0;couter < my_deque.size(); counter++) {
    my_deque[counter].resize(second_index);
      }
      my_container = DEQUE;
    }    
  }
private:
  enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR };

  friend class VectorDeque2D_Inner_Set;

  std::vector<std::vector<T> > my_vector;
  std::deque<std::deque<T> > my_deque;
  STORAGE_CONTAINER my_container;

  T& get(int x,int y) { 
    T temp_val;
    if(my_container == VECTOR) {
      temp_val = my_vector[first_index][second_index];
    }
    else if(my_container == DEQUE) {
      temp_val = my_deque[first_index][second_index];
    }

    return temp_val;
  }

};

Наконец, размерный 2D-контейнер! Спасибо, ребята!

Ответ 1

Существует два основных метода:

1) Используйте оператор(), а не оператор [].
Это связано с тем, что оператор() допускает несколько параметров.

class My2D
{
    public:
       int&   operator()(int x,int y)  { return pget(x,y);}
    private:
       int&   pget(int x,int y) { /* retrieve data from 2D storage */ }
};

2) Используйте оператор [], но верните промежуточный объект.
Затем вы можете применить второй оператор [] к промежуточному объекту.

class My2D
{
    public:
       class My2DRow
       {
           My2D& parent;
           int   x;
           public:
               My2DRow(My2D& p, int theX) : parent(p), x(theX) {}     // Just init the temp object
               int& operator[](int y)  const { return parent.pget(x,y);}   // Here we get the value.
       };

       // Return an object that defines its own operator[] that will access the data.
       // The temp object is very trivial and just allows access to the data via operator[]
       My2DRow operator[](int x)        { return My2DRow(*this, x);}
    private:
       friend class My2DRow;
       int&   pget(int x,int y) { /* retrieve data from 2D storage */ }
};

int main()
{
    My2D   data;
    int&   val = data[1][2]; // works fine.

    // This is the same as
    My2D::My2DRow row  = data[1];
    int&          val2 = row[2]; 
}

Я предпочитаю второй метод.
Это происходит потому, что он оставляет исходный код незатронутым и более естественным для чтения (в контексте массива). Конечно, вы платите за простоту на высоком уровне с чуть более сложным кодом, реализующим ваш 2D-массив.

Ответ 2

Как вы можете перегрузить двойные скобки?

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

Например, если у вас есть вектор векторов, работа уже выполнена: vector < vector < something > > перегрузки operator[], который возвращает vector< something >; это, в свою очередь, перегружает оператор скобки (и возвращает объект something), поэтому вы можете просто сделать:

vector<vector<something> > vec;
// ...
something s = vec[2][3];


Пример с прокси-объектом:
template <typename T>
class Container
{
private:
    // ...


public:

    // Proxy object used to provide the second brackets
    template <typename T>
    class OperatorBracketHelper
    {
        Container<T> & parent;
        size_t firstIndex;
    public:
        OperatorBracketHelper(Container<T> & Parent, size_t FirstIndex) : parent(Parent), firstIndex(FirstIndex) {}

        // This is the method called for the "second brackets"
        T & operator[](size_t SecondIndex)
        {
            // Call the parent GetElement method which will actually retrieve the element
            return parent.GetElement(firstIndex, SecondIndex);
        }

    }

    // This is the method called for the "first brackets"
    OperatorBracketHelper<T> operator[](size_t FirstIndex)
    {
        // Return a proxy object that "knows" to which container it has to ask the element
        // and which is the first index (specified in this call)
        return OperatorBracketHelper<T>(*this, FirstIndex);
    }

    T & GetElement(size_t FirstIndex, size_t SecondIndex)
    {
        // Here the actual element retrieval is done
        // ...
    }
}

(добавьте перегруженные методы const везде, где это необходимо:))

Обратите внимание, что с помощью этого метода вы почти ничего не теряете в отношении реализации operator(), поскольку поиск все еще выполняется в одном месте без ограничений на использование двух индексов, имеющих оба индекса в момент выполнения поиск и без возврата "жирных" временных объектов (OperatorBracketHelper имеет такой же размер, как два указателя, и может быть легко оптимизирован компилятором).

Ответ 3

В С++ нет оператора "с двойными скобками". Что вам нужно сделать, так это определить один оператор [] и вернуть ссылку на другой объект, который, в свою очередь, может реагировать на собственный оператор []. Это может быть вложенно столько уровней, сколько вам нужно.

Например, когда вы создаете вектор векторов, оператор [] на внешнем векторе возвращает ссылку на один из внутренних векторов; оператор [] на этом векторе возвращает ссылку на отдельный элемент вектора.

std::vector<std::vector<float> > example;
std::vector<float> & first = example[0];  // the first level returns a reference to a vector
float & second = example[0][0];  // the same as first[0]

Ответ 4

Не перегружайте оператор [], перегружайте оператор ().

Смотрите эту ссылку: Перегрузка оператора подстроки

Я настоятельно рекомендую прочитать С++ FAQ Lite хотя бы один раз перед отправкой в ​​Stack Overflow. Кроме того, поиск может также дать некоторую полезную информацию.

Ответ 5

Я рассмотрел перегрузку оператора [] для многомерного массива в ответе на предыдущий вопрос.

Я бы, вероятно, имел дело с итераторами довольно похоже: пусть есть один итератор, представляющий "срез" (строка или столбец) многомерного массива, а затем другой, который представляет элемент в этом срезе.