Эффективные альтернативы для демонстрации Коллекции - PullRequest
12 голосов
/ 05 сентября 2008

В C ++, какие у меня есть альтернативы для раскрытия коллекции с точки зрения производительности и целостности данных?

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

  • Нужно ли выбирать между производительностью и целостностью данных?
  • Если так, то вообще лучше идти в одном направлении или это особый случай?
  • Есть ли другие альтернативы?

Ответы [ 10 ]

7 голосов
/ 05 сентября 2008

Часто вызывающий объект хочет получить доступ только для перебора коллекции. Извлеките страницу из книги Руби и сделайте итерацию личным аспектом вашего класса.

#include <algorithm>
#include <boost/function.hpp>

class Blah
{
  public:
     void for_each_data(const std::function<void(const mydata&)>& f) const
     {
         std::for_each(myPreciousData.begin(), myPreciousData.end(), f);
     }

  private:
     typedef std::vector<mydata> mydata_collection;
     mydata_collection  myPreciousData;
};

При таком подходе вы ничего не раскрываете о своих внутренних элементах, т. Е. У вас даже есть коллекция.

5 голосов
/ 05 сентября 2008

Ответ RichQ - разумный метод, если вы используете массив, вектор и т. Д.

Если вы используете коллекцию, не проиндексированную по порядковым значениям ... или думаете, что может понадобиться в какой-то момент в ближайшем будущем ... тогда вы можете рассмотреть возможность ваши собственные типы итераторов и связанные begin() / end() методы:

class Blah
{
public:
   typedef std::vector<mydata> mydata_collection;
   typedef myDataCollection::const_iterator mydata_const_iterator;

   // ...

   mydata_const_iterator data_begin() const 
      { return myPreciousData.begin(); }
   mydata_const_iterator data_end() const 
      { return myPreciousData.end(); }

private:
   mydata_collection  myPreciousData;
};

... который вы затем можете использовать обычным способом:

Blah blah;
for (Blah::mydata_const_iterator itr = blah.data_begin();
   itr != blah.data_end();
   ++itr)
{
   // ...
}
3 голосов
/ 05 сентября 2008

Может как то так?

const std::vector<mydata>& getData()
{
  return _myPrivateData;
}

Преимущество здесь в том, что это очень, очень просто и так же безопасно, как вы получаете в C ++. Вы можете разыграть это, как предлагает RobQ, но вы ничего не можете сделать, чтобы кто-то помешал этому, если вы не копируете. Здесь вам придется использовать const_cast, что довольно легко обнаружить, если вы ищете его.

Итераторы, в качестве альтернативы, могут получить почти то же самое, но это более сложно. Единственное дополнительное преимущество использования итераторов здесь (о чем я могу думать) состоит в том, что вы можете лучше инкапсулировать.

2 голосов
/ 05 сентября 2008

Одним из преимуществ решений @ Shog9 и @ RichQ является то, что они отсоединяют клиента от реализации коллекции.

Если вы решите изменить тип своей коллекции на что-то другое, ваши клиенты все равно будут работать.

2 голосов
/ 05 сентября 2008

Использование константной ссылки или общего указателя поможет только в том случае, если содержимое базовой коллекции не изменяется со временем.

Рассмотрим ваш дизайн. Вызывающий действительно должен видеть внутренний массив? Можете ли вы реструктурировать код так, чтобы вызывающий объект сообщал объекту, что делать с массивом? Например, если вызывающая сторона намеревается выполнить поиск в массиве, может ли объект-владелец сделать это?

Вы можете передать ссылку на вектор результата в функцию. На некоторых компиляторах это может привести к немного более быстрому коду.

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

1 голос
/ 05 сентября 2008

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

Во-первых, вы можете просто вернуть const-ссылку на то, чем является ваш контейнер данных, как предложено выше:

const std::vector<T>& getData() { return mData; }

Это имеет недостаток конкретности: вы не можете изменить внутреннее хранение данных без изменения интерфейса вашего класса.

Во-вторых, вы можете вернуть константные указатели к фактическим данным:

const T* getDataAt(size_t index)
{
   return &mData[index];
}

Это немного приятнее, но также требует, чтобы вы обеспечивали вызов getNumItems и защищали от выходящих за пределы индексов. Кроме того, постоянство ваших указателей легко отбрасывается, и ваши данные теперь доступны для чтения и записи.

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

Вероятно, самый простой способ справиться с этим - использовать Boost Range:

typedef vector<T>::const_iterator range_iterator_type;
boost::iterator_range< range_iterator_type >& getDataRange()
{
    return boost::iterator_range(mData.begin(), mData.end());
}

Преимущества диапазонов в том, что они можно компоновать, фильтровать и т. Д., Как вы можете видеть на сайте .

0 голосов
/ 09 июня 2009

Я предлагаю использовать обратные вызовы в соответствии с EnumChildWindows . Вам нужно будет найти какие-то средства, чтобы пользователь не мог изменить ваши данные. Возможно, используйте const указатель / ссылку.

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

MyClass tmp;
for(int i = 0; i < n; i++){
    tmp = elements[i];
    callback(tmp);
}
0 голосов
/ 08 июня 2009

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

Часть 1: Инкапсуляция и вампиры
Часть 2 (для этого требуется бесплатная регистрация): Train Wreck Spotting
Кевлин Хенни

0 голосов
/ 05 сентября 2008

Если у вас есть std::list простых старых данных (то, что .NET назвало бы «типами значений»), тогда возврат константной ссылки на этот список будет нормальным (игнорируя злые вещи, такие как const_cast)

Если у вас есть std::list указателей (или boost::shared_ptr), то это только остановит вас при изменении коллекции, а не элементов в коллекции. Мой C ++ слишком ржавый, чтобы дать вам ответ на этот вопрос: - (

0 голосов
/ 05 сентября 2008

Использование const - разумный выбор. Вы также можете проверить расширенную библиотеку C ++ для их реализации общего указателя. Это обеспечивает преимущества указателей, т. Е. У вас может быть требование вернуть общий указатель на «null», что ссылка не разрешит.

http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

В вашем случае вы должны сделать тип общего указателя const для запрета записи.

...