Pull to refresh

Comments 49

wrap не нужен, достаточно просто переопределить операторы:

template<class T>
std::ostream& operator<<(std::ostream& os, const T& t)
{
	os.write(reinterpret_cast<const char*>(&t), sizeof(T));
	return os;
}

template<class T>
std::istream& operator>>(std::istream& is, T& t)
{
	is.read(reinterpret_cast<char*>(&t), sizeof(T));
	return is;
}


Хотя по мне так удобнее для своих классов переопределить операторы ввода/вывода в поток и не мучиться.
Хотя нет, wrap нужен для того, чтобы отличить, как записывать — «двоично» или «как текст».

Тогда его следовало бы назвать, напримр, binary или octet_stream. Как красиво:
std::copy(arr, arr + 6, std::ostream_iterator< binary<int> >(out));
Я не очень долго думал над названием, это правда: наверное, binary, действительно неплохой вариант.
Так делать ни в коем случае нельзя. Можно напороться на случай, когда включится эта версия вместо перегруженной. И тогда здравствуй дебаггинг.
UFO just landed and posted this here
Название подразумевало не работу, а обеспечение возможности такой работы. Пример работы — в последнем листинге (copy, inner_product).

Каждый раз писать свои классы долго и скучно (код был бы одинаковый с точностью до типов): на помощь приходят шаблоны (об этом сказано в тексте статьи).

Про аналог это правильно. Только, чтобы получить такой аналог в C++ (то есть, чтобы работать с бинарным файлом, хранящимся на диске, как с list), нужно идти на описанные в статье ухищрения.
Если бы вы использовали type_traits и enable_if из boost, то получили бы безопсный вариант без велосипедов.

templatetypename boost::enable_if <typename boost::type_traits::is_pod ::type, std::istream &>::type
operator >>(std::istream& is, T& t) {

}

Также хорошей идеей являются специализации для контейнеров STL:

templatetypename boost::enable_if <typename boost::type_traits::is_pod ::type, std::istream &>::type
operator >>(std::istream& is, std::vector& t) {

}

Также могу посоветовать Google Protocol Buffers для записи данных на диск или передачи по сети — это наиболее простой и удобный вариант.
Побился минут 10, чтобы скомпилировать пример с type_traits/enable_if — не получилось… Хотя опыт использования этого добра есть. Кстати, у вас съедены некоторые угловые скобки.

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

Про Google Protocol Buffers знаю, но никогда не использовал, к сожалению. Пример, выполняющий работу, аналогичную той, что в последнем листинге в статье — приветствуется!
Ну и от меня 5 копеек помимо вышесказанного: плохо что wrap копирует объект Т, а не хранит указатль на него. В данной реализации это ничего бы не ухудшило, но избавило бы от лишнего копирования и вообще использования дополниельной памяти, линейно зависящей от размера T.
Я думал об этом, да. А вы не задумывались, например, какой указатель хранить, константный или нет?

И вы уверены, что память, на которую он будет указывать, всегда будет валидна? То есть не получится указателя на локальные ресурсы?

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

Кроме того, я думаю, что хороший компилятор постарается соптимизировать так, чтобы затраты на wrap были минимальны или их вообще не было (я думаю, это вполне возможно). В общем, интересно было бы, если бы кто-то сравнил. Но я, как уже говорил, сходу такое просто не могу написать.
Константного вполне хватит.
Да, уверен, сами посмотрите — wrap создается в момент вызова функции записи, из функции, в которой доступен объект. Пока функция записи не завершится, не завершится и внешняя функция, значит объект из стека не будет потерт.

Затраты МОГУТ быть велики если объект болшой. Не надо надеяться на чудо — просто сделайте указатель, который 100% будет работать и всегда по меньшей мере не медленнее.

Нет, компилятор не сможет это оптимизировать, он обязан копировать — мало ли какйо у вас там нестандартный конструктор копирования, может ссылки считает — не надо надеяться на магию.
Как де я буду писать в память по константному указателю? (Под «константым указателем», я прошу прощения, имел ввиду указатель на константу.)

Про оптимизации вы зря так уверены. Для GCC (под которым я работаю) если специально не отключать оптимизацию RVO, то вызов даже нетривиального копиктора будет “optimized-out” (можете посмотреть соответствующий пример в Вкипедии: RVO: Summary). Только не надо, пожалуйста, ругаться на GCC :)
Что такое rvo я знаю. Но как он вам поможет при создании объекта?

RVO это оптимизация ВОЗВРАЩАЕМОГО значения из фукнции. Не будет копирования из return во временный объект и из временного объекта в тот, в который приваиваете возврат функции.

RVO никакого отношения к копированию внутрь wrap не имеет — не фантазируйте, пожалуйста.
Это просто один пример оптимизации, которая исключает вызов нетривиального копиктора, — такой оптимизации, которая, следуя вашей логике, просто не может существовать:
> Нет, компилятор не сможет это оптимизировать, он обязан копировать — мало ли какйо у вас там нестандартный конструктор копирования
так что и логика не верна.

В целом, убеждать вас в мощи оптимизации современных компиляторов у меня желания нет. Я просто утверждаю, что в моих частных задачах оверхед (который, я всё ещё утверждаю, вам не удастся легко избежать: на вопрос про указатель на константу вы так и не ответили) не существенен. Если в вашей задаче он существенен — поступайте как считаете нужным…
Пожалуйста, перестаньте верить в магию.
Компилятор НЕ ИМЕЕТ ПРАВА убирать копирования.

RVO это оптимизация, описанная в стандарте — именно поэтому она существует и ее поддерживают все современные компиляторы (не только гцц).
Сделана была она давно по той же причине, для чего в C++-11 описана move semantics — избегание копирования ВРЕМЕННОГО объекта.

А проще — напишите простой пример — создайте класс T (любой), добавьте в него отладочный вывод при копировании.
И напишите T a = Foo(), где Foo() возвращает объект типа T — копирования не будет.
А с wrap как не крутите — будет.

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

Просто почитайте книги по компиляторам и с++, а не читайте форумы урывками и черные книги с магией =)
Вы не совсем точны, стандарт позволяет компиляторам делать Copy elision не только в случаях RVO.

Еще одно допустимое условие — вызов копирующего/move конструктора может опускаться, если объект не связан ссылкой в коде).

Кроме того, С++11 позволяет избегать и move-конструктора + добавляется еще два условия для Copy elision, связанные с обработкой исключений.

§12.8/31
Я думаю, что для таких любителей оптимизаций вполне подойдет boost::ref / std::ref.
Ну кстати да.
Насчет комментария с ссылкой на стандарт выше (вот, кстати, совсем другой с вами разговор) — о какой редакции идет речь?

В черновике с++-11 яно не то в данном пункте.
Хм, не черновик, а окончательная версия у меня, насколько я вижу.

ISO+IEC+14882-2011.pdf
Это небольшой хинт, как найти текст, кхм.
Сам не пользовался, но полагаю что вам бы подошел STLXXL.
Спасибо, любопытно. Примеры на сайте STLXX кажутся весьма многословными и не очень интуитивными. Интересно было бы посмотреть на код делающий то же, что в статье (последний листинг).
Спасибо, да, вот на это я как раз посмотрел. Что пугает: определение my_type (сложнее, чем хотелось бы; обязательно ли min/max_value?…), используют не стандартные алгоритмы (для сортировки, например), а свои: с моей точки зрения, это слегка противоречит идеологии, когда если ты реализуешь специальный контейнер (их вектор, отмапленный на файл), то предоставляя итераторы по нему, можешь пользоваться стандартными алгоритмами. У нас, в частности, была задача работать стандартными алгоритмами. Но мысль ясна, спасибо.
«Что пугает: определение my_type»

Так с обычным int не интересно :) Min/Max_value нужно скорее всего для внешней сортировки.

«Что пугает: … используют не стандартные алгоритмы (для сортировки, например), а свои»

Так там наверно n-фазная сортировка реализована — не уверен, что это можно было бы сделать специализацией обыкновенного std::sort. AFAIK у них STL-совместимые итераторы и можно и обычный sort использовать.

PS. Мне честно говоря не понятно, зачем рассказывать детям про типизированные файлы a-la pascal. Какая-то чисто академическая экзотика. Какой смысл засунуть в файл 5/500/50000 интов, если они отлично в памяти помещаются? Засунули в файл 5 интов, а что дальше? Смотреть на них через hexdump и переться? На диск имеет смысл выходить, когда обрабатываемые данные не помещаются в RAM. Не знаю, подходит ли STXXL для production; но для обучения — самое то ИМХО.
Про смысл обучения этому я не хотел бы спорить по разным причинам, но насчёт использования при обучении STXXL — я (как человек работавший три года в университете с младшекурсниками) сильно сомневаюсь: тяжеловато для старших школьников/младшекурсников (для которых, как сказано в статье, это делалось). Возможно, я ошибаюсь, это только моё ощущение.
Чтобы хранить эти 5/500/50000 интов между запусками программы?

Да, есть в большинстве языков текстовая сериализация, но это лишние накладные расходы.
Забавный пост. Все, что написано — это велосипед, причем пронафталиненный велосипед. Посмотрите в сторону boost::serialization, и вы поймете, что это уже давно сделано на более профессиональном уровне с учетом различных контейнеров, shared_ptr, полиморфизма и поддержкой версионности.
Если вас не затруднит, приведите, пожалуйста, пример с Boost.Serialization, который делал бы аналогичное тому, что приведено в последнем листинге.
Основная идея — сначала сериализуем в контейнер, а затем проверяем. Можно, конечно, заявить, что это не совсем то. Но на самом деле как правило нужна именно сериализация, т.к. операции в памяти делаются гораздо быстрее, чем в файле. Предположим, что надо отсортировать данные файла, которые представляют собой int. Мне сложно представить, как это делается указанным выше способом. В boost:serialization это делается просто.
> сначала сериализуем в контейнер
Наверное, вы хотели сказать «десериализуем». В принципе, вы правильно поняли, что
> это не совсем то
Зачем мне какой-то дополнительный контейнер, если я хочу посчитать сумму, найти максимум, создать новый файл, который будет содержать только чётные числа из старого? Зачем мне дополнительная сущность в виде контейнера при решении каждой этой задачи? Бритва Оккамма решает…
А если надо отсортировать числа, то что тогда? Этот способ работает только в очень ограниченных случаях:
1. Возможно использование только Forward Iterators. Другие, такие как Reverse Iterator, Bidirectional, Random нельзя.
2. Контейнер содержит только old-plain data. Это тоже очень сильное ограничение, никакие классы, ООП и проч. нельзя использовать.
3. При всей кажущейся оптимальности, данный способ будет проходить по всем элементам, невзирая на то, что можно остановиться на первом элементе. Если же сделать реализацию, чтобы он останавливался, то тогда возникает следующая сложность: а что, если у нас контейнер содержит несколько контейнеров? Что тогда? Как перейти от первого ко второму?

Да, для частного использования частного вида задач это и можно использовать. Но для общего случая этот способ не очень подходит. Поэтому хотелось бы видеть не только идею, но и анализ: когда можно использовать, когда нельзя, какие ограничения. А также в чем выигрыш по сравнению с десериализацией и проверкой в контенере.
Большую часть ваших претензий можно предъявить к любым итераторам потоков ввода-вывода. Таковы принципы работы с потоками C++ и STL…
А при чем тут потоки? Это реализация задумки — что-то сделать с данными из файла. Если сначала десериализовать, то этих ограничений нет. Хотя там тоже есть потоки. Парадокс, не так ли?

Т.е. я хочу сказать, что у предложенного метода есть ограничения, связанные с приведенной реализацией. При другой реализации их нет.
Помню, когда я был в школе, я был очень рад, когда наконец узнал про замечательные функции fread, fwrite, которые делают то, что мне надо, легко и просто, т.е. без жутких конструкций «std::ostream_iterator< wrap >».

Конечно, извращаться — это интересно. Но (исключительно примеряя себя) гораздо лучше понимается простая запись и чтение структур: fwrite(&object, sizeof(object), 1, f); Да и весьма себе Pascal-style.
Как раз так делать нельзя. Почему-то под object всегда понимается old-plain structure. А если это класс, который содержит всякие вектора и мапы? А если в функцию передали ссылку на производный класс, а сама ссылка является ссылкой на базовый? Это прокатывает только в простейшем случае, который в большинстве случаев неинтересен.
Тогда сразу проще использовать protobuf, мне кажется, или xml сериализацию. В бинарные файлы удобно записывать именно какой-нибудь огромный массив int, float, bool после, например, численных расчетов--только тогда это удобнее и быстрее.
Так то, что написано в статье, и есть эквивалент fread/fwrite, только завёрнутое в STL.
Это точно. В этом и была цель, как ни странно!
Так я и не спорю, это просто к тому что комментарий выше «Как раз так делать нельзя» одинаково применим к обоим случаям.
Ой, извиняюсь: мне показалось, что это был ответ к моему комментарию.
Вам не нравится STL. Это нормально. Но статья явно не направлена на то, чтобы в очередной раз разжигать этот холивар.
В академических целях есть более ключевые вещи, чем нравится-не нравится. На начальных этапах код должен быть кратким, ясным, читабельным, как можно более интуитивным.
Если Ваш опыт говорит о том, что чем больше абстракций при обучении — тем лучше понимание в головах детей, то я признаю, что был не прав.
Ну, зачем вы приписываете мне то, чего я и близко не говорил. Детей просто надо учить C++ и STL. Это не я придумал: программа такая, и мне неинтересно обсуждать эту программу: тема не об этом. fread/fwrite это не STL, так что в данной теме это также оффтоп (от чего я и предостерегал в заключении статьи), хотя этому тоже, конечно, надо учить.
В этом случае, конечно, static_cast не опасный, но по смыслу больше бы подошло то, что я называю implicit_cast:
template<class T, class U>
T implicit_cast(U p) { return p; }

Либо вообще добавить методы вроде get() которые возвращают T&, и тогда касты никакие не нужны, они только с толку сбивают.
Кстати, пример у меня работает и без operator T &(). Без operator T const &() const не компилируется, это понятно. А неконстантный вообще там нужен? (кроме, конечно, места, где делается static_cast к T&)
> Кстати, пример у меня работает и без operator T &().
Эмм, operator>> как написать без неконстантного преобразования? (У меня не компилируется даже, естественно.)

Насчёт implicit_cast не понял: как без реализации операции преобразования типа это будет работать.

Насчёт get: спасибо, хороший вопрос (наверное, добавлю в текст статьи). Именно преобразования нужны в примерах типа:
    std::ifstream in("f.dat");
    int arr2[6];
    std::copy(std::istream_iterator< wrap<int> >(in),
            std::istream_iterator< wrap<int> >(), arr2);
implicit_cast — я имел в виду вместо static_cast, но с преобразованиями типа. То есть просто заменив в вашем коде static_cast на implicit_cast.
Действительно, неконстантное понадобится если итератор использовать для вывода, а не для ввода…
Sign up to leave a comment.

Articles