Могу ли я использовать оператор [] в C ++ для создания виртуальных массивов - PullRequest
6 голосов
/ 11 мая 2010

У меня есть большая кодовая база, изначально C, перенесенная на C ++ много лет назад, которая работает с несколькими большими массивами пространственных данных. Эти массивы содержат структуры, представляющие точечные и треугольные объекты, которые представляют модели поверхностей. Мне нужно реорганизовать код так, чтобы конкретный способ внутреннего хранения этих объектов различался для конкретных сценариев. Например, если точки лежат на регулярной плоской сетке, мне не нужно хранить координаты X и Y, так как они могут быть вычислены на лету, как и треугольники. Точно так же я хочу воспользоваться преимуществами таких основных инструментов, как STXXL для хранения. Самый простой способ сделать это - заменить доступ к массиву функциями типа put и get, например,

point[i].x = XV;

становится

Point p = GetPoint(i);
p.x = XV;
PutPoint(i,p);

Как вы можете себе представить, это очень утомительный рефакторинг с большой кодовой базой, подверженный всевозможным ошибкам в пути. Я хотел бы написать класс, который имитирует массив, перегружая оператор []. Поскольку массивы уже находятся в куче и перемещаются с помощью reallocs, код уже предполагает, что ссылки на массив, такие как

point *p = point + i;

не может использоваться. Можно ли написать этот класс? Например, написание методов ниже в терминах оператора [];

void MyClass::PutPoint(int Index, Point p)
{
   if (m_StorageStrategy == RegularGrid)
   {
      int xoffs,yoffs;
      ComputeGridFromIndex(Index,xoffs,yoffs);
      StoreGridPoint(xoffs,yoffs,p.z);
    } else
       m_PointArray[Index] = p;   
  }
}

Point MyClass::GetPoint(int Index)
{
   if (m_StorageStrategy == RegularGrid)
   {
      int xoffs,yoffs;
      ComputeGridFromIndex(Index,xoffs,yoffs);
      return GetGridPoint(xoffs,yoffs);   // GetGridPoint returns Point
    } else
       return m_PointArray[Index];   
  }
}

Меня беспокоит то, что все классы массивов, которые я видел, имеют тенденцию передавать по ссылке, тогда как я думаю, что мне придется передавать структуры по значению. Я думаю, что это должно работать, а не производительность, кто-нибудь может увидеть какие-либо серьезные подводные камни с этим подходом. нотабене причина, по которой я должен передать значение, это получить

point[a].z = point[b].z + point[c].z

для правильной работы там, где изменяется базовый тип хранилища.

Ответы [ 4 ]

5 голосов
/ 11 мая 2010

Вам не нужно передавать массив по значению. Для изменения значений в массиве вам нужны две версии operator[], одна из которых возвращает ссылку (для изменения), а другая - постоянную ссылку.

В принципе нет причин не использовать operator[], если вам не нужно изменять тип хранилища во время выполнения - нет виртуальных операторов, поэтому вам понадобится именованная функция, если вы хотите полиморфизм во время выполнения. В этом случае вы можете создать простой struct, который адаптирует вызовы оператора к вызовам функций (хотя это скорее зависит от API хранилища - если код предполагает, что назначение переменных-членов точки изменяет хранимые данные, возможно, вам придется сделайте тип точки тоже переменной шаблона, чтобы ее можно было переопределить).

Глядя на ваш пример кода, у него есть тест для стратегии хранения. Не делайте этого. Либо используйте OO, и ваш объект хранения реализует общий виртуальный интерфейс, либо (возможно, лучше) используйте шаблонное программирование для изменения механизма хранения.

Если вы посмотрите на гарантии, сделанные std::vector (в более поздних стандартах C ++), то возможно иметь что-то, что имеет динамическое хранилище и позволяет использовать арифметику указателей, хотя это требует непрерывного хранения. Учитывая, что некоторые из ваших значений создаются на лету, вероятно, не стоит накладывать это ограничение на ваши реализации, но само ограничение не препятствует использованию operator[].

2 голосов
/ 11 мая 2010

То, что вы хотите, возможно, но, поскольку вам также необходим доступ для записи, результат иногда будет немного более сложным. Вам нужна функция установки, которая возвращает не прямой «Точечный доступ для записи», а временную копию, которая выполняет запись, как только копия выходит из области видимости.

Следующий фрагмент кода пытается наметить решение:

class PointVector
{
  MyClass container_;

  public:
  class PointExSet: public Point
  {
    MyClass &container_;
    int index_;

    public:
    PointExSet(MyClass &container, int index)
      :Point(container.GetVector(index)),container_(container),index_(index)
    {
    }

    ~PointExSet()
    {
      container_.PutVector(index_) = *this;
    }
  };

  PointExSet operator [] (int i)
  {
    return PointExSet(container_,i);
  }
};

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

1 голос
/ 11 мая 2010

Чтобы иметь полный контроль над операциями с массивом, оператор [] должен вернуть специальный объект (изобретенный давно и называемый «курсор»), который будет обрабатывать операции за вас. Как пример:

class Container
{
  PointCursor operator [] (int i)
  {
    return PointCursor(this,i);
  }
};
class PointCursor
{
public:
    PointCursor(_container, _i)
       : container(_container), i(_i),
         //initialize subcursor
         x(container, i) {}     

    //subcursor
    XCursor x;
private:
   Container* container;
   int i;
};
class XCursor
{
public:
    XCursor(_container, _i)
      : container(_container), i(_i) {}

     XCursor& operator = (const XCursor& xc)
     {
          container[i].x = xc.container[xc.i].x;
          //or do whatever you want over x
     }

     Container* container;
     int i; 
}
//usage
my_container[i].x = their_container[j].x; //calls XCursor::operator = ()
0 голосов
/ 11 мая 2010

Прочитав приведенные выше ответы, я решил, что ответ Пита с двумя версиями operator[] - лучший путь вперед. Для обработки изменений между типами во время выполнения я создал новый класс шаблона массива, который принял четыре параметра следующим образом:

template<class TYPE, class ARG_TYPE,class BASE_TYPE, class BASE_ARG_TYPE>
class CMorphArray 
{
int GetSize() { return m_BaseData.GetSize(); }
BOOL IsEmpty() { return m_BaseData.IsEmpty(); }

// Accessing elements
const TYPE& GetAt(int nIndex) const;
TYPE& GetAt(int nIndex);
void SetAt(int nIndex, ARG_TYPE newElement);
const TYPE& ElementAt(int nIndex) const;
TYPE& ElementAt(int nIndex);

// Potentially growing the array
int Add(ARG_TYPE newElement);

// overloaded operator helpers
const TYPE& operator[](int nIndex) const;
TYPE& operator[](int nIndex);

   CBigArray<BASE_TYPE, BASE_ARG_TYPE>  m_BaseData;
private:
   CBigArray<TYPE, ARG_TYPE>    m_RefCache;
   CBigArray<int, int&> m_RefIndex;
   CBigArray<int, int&> m_CacheIndex;

   virtual void Convert(BASE_TYPE,ARG_TYPE) = 0;
   virtual void Convert(TYPE,BASE_ARG_TYPE) = 0;

   void InitCache();
   TYPE&    GetCachedElement(int nIndex);
};

Основное хранилище данных находится в m_BaseData, который представляет собой данные в их собственном формате, который может различаться по типу, как обсуждалось. m_RefCache - это вторичный массив для кэширования элементов в ожидаемом формате, а функция GetCachedElement использует виртуальные функции Convert для перевода данных при их перемещении в кеш и из него. Кэш должен быть как минимум таким же большим, как и количество одновременных ссылок, которые могут быть активны в любой момент времени, но в моем случае, вероятно, выиграет от его увеличения, поскольку это уменьшает количество требуемых преобразований. Хотя реализация курсора Алска, вероятно, работала бы хорошо, данное решение требует меньше копий объектов и временных переменных и должно обеспечивать немного лучшую производительность, что важно в этом случае.

Приношу извинения всем поклонникам STL за старый внешний вид MFC; остальная часть проекта - MFC, поэтому в этом случае имеет больше смысла. CBigArray был результатом связанного вопроса переполнения стека , который стал основой моей обработки большого массива. Я надеюсь завершить внедрение сегодня и проверить завтра. Если меня это не устраивает, я отредактирую это сообщение должным образом.

...