Как определить оператор двойных скобок / двойного итератора, похожий на вектор векторов? - PullRequest
11 голосов
/ 21 сентября 2010

Я портирую код, который использует очень большой массив чисел с плавающей запятой, который может вызывать сбои malloc от c до c ++.Я задал вопрос о том, использовать ли мне векторы или deques, и 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;
    }
  }
};

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

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] прямой доступ к тому, что real контейнер памяти, который я использую (определяется значением моей перечисляемой переменной.

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

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

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

РЕДАКТИРОВАТЬ 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 контейнер !!Спасибо, ребята!

Ответы [ 5 ]

20 голосов
/ 21 сентября 2010

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

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-массив.

4 голосов
/ 21 сентября 2010
Как перегрузить двойные скобки?

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

Например, если у вас есть вектор векторов, работа уже выполнена: 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 такой же большой, как два указателя, и может быть легко оптимизирован компилятором).

2 голосов
/ 21 сентября 2010

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

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

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]
1 голос
/ 21 сентября 2010

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

См. Эту ссылку: Оператор перегрузки Subscript.

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

0 голосов
/ 21 сентября 2010

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

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

...