Эффективные альтернативы для предоставления доступа к коллекции

StackOverflow https://stackoverflow.com/questions/44693

Вопрос

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

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

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

Решение

Ответ РичкА это разумный метод, если вы используете массив, вектор и т.д.

Если вы используете коллекцию, которая не индексируется по порядковым значениям...или думаешь, что ты возможно, потребуется в какой-то момент в ближайшем будущем...тогда вы, возможно, захотите рассмотреть возможность предоставления вашего собственного типа (ов) итератора и связанных с ним 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)
{
   // ...
}

Другие советы

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

#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;
};

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

Может быть, что-то вроде этого?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

MyClass tmp;
for(int i = 0; i < n; i++){
    tmp = elements[i];
    callback(tmp);
}

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

Часть 1: Инкапсуляция и Вампиры
Часть 2 (теперь для прочтения этой статьи требуется бесплатная регистрация): Обнаружение крушения поезда
автор : Кевлин Хенни

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top