Какие у меня есть альтернативы в C++ для раскрытия коллекции с точки зрения производительности и целостности данных?
Моя проблема в том, что я хочу вернуть вызывающему абоненту внутренний список данных, но не хочу создавать копию. Thant оставляет мне либо ссылку на список, либо указатель на список. Однако я не без ума от того, чтобы позволить вызывающему абоненту изменять данные, я просто хочу, чтобы он считывал данные.





Может как то так?
const std::vector<mydata>& getData()
{
return _myPrivateData;
}
Преимущество здесь в том, что это очень, очень просто и так же безопасно, как и в C++. Вы можете использовать это, как предлагает RobQ, но вы ничего не можете сделать, чтобы кому-то это помешало, если вы не копируете. Здесь вам придется использовать const_cast, который довольно легко обнаружить, если вы его ищете.
В качестве альтернативы итераторы могут дать вам примерно то же самое, но это более сложно. Единственное дополнительное преимущество использования здесь итераторов (которое я могу придумать) заключается в том, что у вас может быть лучшая инкапсуляция.
Использование const - разумный выбор. Вы также можете проверить библиотеку boost C++ для их реализации общего указателя. Он обеспечивает преимущества указателей, то есть у вас может быть требование вернуть общий указатель на "null", чего не разрешила бы ссылка.
http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm
В вашем случае вы должны сделать тип общего указателя const, чтобы запретить запись.
Ответ 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)
{
// ...
}
Если у вас есть std::list с простыми старыми данными (то, что .NET назвал бы `` типами значений ''), тогда возврат константной ссылки на этот список будет прекрасным (игнорируя злые вещи, такие как const_cast)
Если у вас есть указатели std::list (или boost::shared_ptr), это остановит вас только от изменения коллекции, а не элементов в коллекции. Мой C++ слишком ржавый, чтобы сказать вам ответ на этот момент :-(
Использование константной ссылки или общего указателя поможет только в том случае, если содержимое базовой коллекции не меняется с течением времени.
Обдумайте свой дизайн. Действительно ли вызывающему абоненту нужно видеть внутренний массив? Можете ли вы реструктурировать код так, чтобы вызывающий сообщал объекту, что делать с массивом? Например, если вызывающая сторона намеревается выполнить поиск в массиве, может ли это сделать объект-владелец?
Вы можете передать в функцию ссылку на вектор результата. В некоторых компиляторах это может привести к немного более быстрому коду.
Я бы порекомендовал сначала попытаться изменить дизайн, во-вторых, использовать чистое решение, а в-третьих, оптимизировать производительность (при необходимости).
Вам нужен доступ только для чтения без копирования всего большого двоичного объекта данных. У вас есть пара вариантов.
Во-первых, вы можете просто вернуть константную ссылку на ваш контейнер данных, как предложено выше:
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());
}
Это дает преимущества в том, что диапазоны можно компоновать, фильтровать и т. д., Как вы можете видеть на интернет сайт.
Одним из преимуществ решений @ Shog9 и @ RichQ является то, что они отделяют клиента от реализации коллекции.
Если вы решите изменить тип коллекции на что-то другое, ваши клиенты все равно будут работать.
Часто вызывающий хочет получить доступ просто для перебора коллекции. Возьмите страницу из книги 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;
};
При таком подходе вы ничего не раскрываете о своих внутренних компонентах, т.е. что вы даже имеют коллекцию.
@JoBates Я больше не использую C++ изо дня в день, пожалуйста, не стесняйтесь редактировать мой ответ, чтобы обновить его!
В следующих двух статьях подробно рассматриваются некоторые проблемы, связанные с инкапсуляцией контейнерных классов, и необходимость инкапсуляции. Хотя они не обеспечивают полностью проработанного решения, по сути, они ведут к тому же подходу, что и Shog9.
Часть 1: Инкапсуляция и вампиры
Часть 2 (теперь требуется бесплатная регистрация для чтения): Обнаружение крушения поезда
Кевлин Хенни
К вашему сведению, ссылки, которые вы разместили, мертвы.
@ ММ. Спасибо за уведомление, теперь исправлено.
Я предлагаю использовать обратные вызовы в соответствии с EnumChildWindows. Вам нужно будет найти какие-то средства, чтобы запретить пользователю изменять ваши данные. Возможно, используйте указатель / ссылку const.
С другой стороны, вы можете передать копию каждого элемента функции обратного вызова, каждый раз перезаписывая копию. (Вы не хотите создавать копию всей своей коллекции. Я предлагаю делать копию только по одному элементу за раз. Это не займет много времени / памяти).
MyClass tmp;
for(int i = 0; i < n; i++){
tmp = elements[i];
callback(tmp);
}
Я знаю, что это старый ответ, но теперь я делаю это с помощью std :: function:
void for_each_data(const std::function<void(const mydata&)>& f) const;