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. Как красиво:
Тогда его следовало бы назвать, напримр, binary или octet_stream. Как красиво:
std::copy(arr, arr + 6, std::ostream_iterator< binary<int> >(out));
Так делать ни в коем случае нельзя. Можно напороться на случай, когда включится эта версия вместо перегруженной. И тогда здравствуй дебаггинг.
Название подразумевало не работу, а обеспечение возможности такой работы. Пример работы — в последнем листинге (copy, inner_product).
Каждый раз писать свои классы долго и скучно (код был бы одинаковый с точностью до типов): на помощь приходят шаблоны (об этом сказано в тексте статьи).
Про аналог это правильно. Только, чтобы получить такой аналог в C++ (то есть, чтобы работать с бинарным файлом, хранящимся на диске, как с list), нужно идти на описанные в статье ухищрения.
Каждый раз писать свои классы долго и скучно (код был бы одинаковый с точностью до типов): на помощь приходят шаблоны (об этом сказано в тексте статьи).
Про аналог это правильно. Только, чтобы получить такой аналог в 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 для записи данных на диск или передачи по сети — это наиболее простой и удобный вариант.
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 знаю, но никогда не использовал, к сожалению. Пример, выполняющий работу, аналогичную той, что в последнем листинге в статье — приветствуется!
Кроме того, подозреваю, что в этом варианте также, как в примере выше в комментариях, потеряем возможность работы с текстовыми файлами.
Про Google Protocol Buffers знаю, но никогда не использовал, к сожалению. Пример, выполняющий работу, аналогичную той, что в последнем листинге в статье — приветствуется!
Ну и от меня 5 копеек помимо вышесказанного: плохо что wrap копирует объект Т, а не хранит указатль на него. В данной реализации это ничего бы не ухудшило, но избавило бы от лишнего копирования и вообще использования дополниельной памяти, линейно зависящей от размера T.
Я думал об этом, да. А вы не задумывались, например, какой указатель хранить, константный или нет?
И вы уверены, что память, на которую он будет указывать, всегда будет валидна? То есть не получится указателя на локальные ресурсы?
В общем, такая реализация (если её вообще можно сделать, в чём я не уверен сходу), будет сложней, чем моя. Семантика значения сильно проще во многих отношениях. С другой стороны, тут затраты на wrap не будут велики (особенно в случае встроенных типов, которые не сильно больше или даже меньше, чем указатель), и для моих задач эти затраты не играю никакой роли.
Кроме того, я думаю, что хороший компилятор постарается соптимизировать так, чтобы затраты на wrap были минимальны или их вообще не было (я думаю, это вполне возможно). В общем, интересно было бы, если бы кто-то сравнил. Но я, как уже говорил, сходу такое просто не могу написать.
И вы уверены, что память, на которую он будет указывать, всегда будет валидна? То есть не получится указателя на локальные ресурсы?
В общем, такая реализация (если её вообще можно сделать, в чём я не уверен сходу), будет сложней, чем моя. Семантика значения сильно проще во многих отношениях. С другой стороны, тут затраты на wrap не будут велики (особенно в случае встроенных типов, которые не сильно больше или даже меньше, чем указатель), и для моих задач эти затраты не играю никакой роли.
Кроме того, я думаю, что хороший компилятор постарается соптимизировать так, чтобы затраты на wrap были минимальны или их вообще не было (я думаю, это вполне возможно). В общем, интересно было бы, если бы кто-то сравнил. Но я, как уже говорил, сходу такое просто не могу написать.
Константного вполне хватит.
Да, уверен, сами посмотрите — wrap создается в момент вызова функции записи, из функции, в которой доступен объект. Пока функция записи не завершится, не завершится и внешняя функция, значит объект из стека не будет потерт.
Затраты МОГУТ быть велики если объект болшой. Не надо надеяться на чудо — просто сделайте указатель, который 100% будет работать и всегда по меньшей мере не медленнее.
Нет, компилятор не сможет это оптимизировать, он обязан копировать — мало ли какйо у вас там нестандартный конструктор копирования, может ссылки считает — не надо надеяться на магию.
Да, уверен, сами посмотрите — wrap создается в момент вызова функции записи, из функции, в которой доступен объект. Пока функция записи не завершится, не завершится и внешняя функция, значит объект из стека не будет потерт.
Затраты МОГУТ быть велики если объект болшой. Не надо надеяться на чудо — просто сделайте указатель, который 100% будет работать и всегда по меньшей мере не медленнее.
Нет, компилятор не сможет это оптимизировать, он обязан копировать — мало ли какйо у вас там нестандартный конструктор копирования, может ссылки считает — не надо надеяться на магию.
Как де я буду писать в память по константному указателю? (Под «константым указателем», я прошу прощения, имел ввиду указатель на константу.)
Про оптимизации вы зря так уверены. Для GCC (под которым я работаю) если специально не отключать оптимизацию RVO, то вызов даже нетривиального копиктора будет “optimized-out” (можете посмотреть соответствующий пример в Вкипедии: RVO: Summary). Только не надо, пожалуйста, ругаться на GCC :)
Про оптимизации вы зря так уверены. Для GCC (под которым я работаю) если специально не отключать оптимизацию RVO, то вызов даже нетривиального копиктора будет “optimized-out” (можете посмотреть соответствующий пример в Вкипедии: RVO: Summary). Только не надо, пожалуйста, ругаться на GCC :)
Что такое rvo я знаю. Но как он вам поможет при создании объекта?
RVO это оптимизация ВОЗВРАЩАЕМОГО значения из фукнции. Не будет копирования из return во временный объект и из временного объекта в тот, в который приваиваете возврат функции.
RVO никакого отношения к копированию внутрь wrap не имеет — не фантазируйте, пожалуйста.
RVO это оптимизация ВОЗВРАЩАЕМОГО значения из фукнции. Не будет копирования из return во временный объект и из временного объекта в тот, в который приваиваете возврат функции.
RVO никакого отношения к копированию внутрь wrap не имеет — не фантазируйте, пожалуйста.
Это просто один пример оптимизации, которая исключает вызов нетривиального копиктора, — такой оптимизации, которая, следуя вашей логике, просто не может существовать:
> Нет, компилятор не сможет это оптимизировать, он обязан копировать — мало ли какйо у вас там нестандартный конструктор копирования
так что и логика не верна.
В целом, убеждать вас в мощи оптимизации современных компиляторов у меня желания нет. Я просто утверждаю, что в моих частных задачах оверхед (который, я всё ещё утверждаю, вам не удастся легко избежать: на вопрос про указатель на константу вы так и не ответили) не существенен. Если в вашей задаче он существенен — поступайте как считаете нужным…
> Нет, компилятор не сможет это оптимизировать, он обязан копировать — мало ли какйо у вас там нестандартный конструктор копирования
так что и логика не верна.
В целом, убеждать вас в мощи оптимизации современных компиляторов у меня желания нет. Я просто утверждаю, что в моих частных задачах оверхед (который, я всё ещё утверждаю, вам не удастся легко избежать: на вопрос про указатель на константу вы так и не ответили) не существенен. Если в вашей задаче он существенен — поступайте как считаете нужным…
Пожалуйста, перестаньте верить в магию.
Компилятор НЕ ИМЕЕТ ПРАВА убирать копирования.
RVO это оптимизация, описанная в стандарте — именно поэтому она существует и ее поддерживают все современные компиляторы (не только гцц).
Сделана была она давно по той же причине, для чего в C++-11 описана move semantics — избегание копирования ВРЕМЕННОГО объекта.
А проще — напишите простой пример — создайте класс T (любой), добавьте в него отладочный вывод при копировании.
И напишите T a = Foo(), где Foo() возвращает объект типа T — копирования не будет.
А с wrap как не крутите — будет.
Просто попробуйте и убедитесь. Я говорю это все потому, что знаю — эксперементировал по работе, а не верю в чудеса и умные компиляторы.
Просто почитайте книги по компиляторам и с++, а не читайте форумы урывками и черные книги с магией =)
Компилятор НЕ ИМЕЕТ ПРАВА убирать копирования.
RVO это оптимизация, описанная в стандарте — именно поэтому она существует и ее поддерживают все современные компиляторы (не только гцц).
Сделана была она давно по той же причине, для чего в C++-11 описана move semantics — избегание копирования ВРЕМЕННОГО объекта.
А проще — напишите простой пример — создайте класс T (любой), добавьте в него отладочный вывод при копировании.
И напишите T a = Foo(), где Foo() возвращает объект типа T — копирования не будет.
А с wrap как не крутите — будет.
Просто попробуйте и убедитесь. Я говорю это все потому, что знаю — эксперементировал по работе, а не верю в чудеса и умные компиляторы.
Просто почитайте книги по компиляторам и с++, а не читайте форумы урывками и черные книги с магией =)
Вы не совсем точны, стандарт позволяет компиляторам делать Copy elision не только в случаях RVO.
Еще одно допустимое условие — вызов копирующего/move конструктора может опускаться, если объект не связан ссылкой в коде).
Кроме того, С++11 позволяет избегать и move-конструктора + добавляется еще два условия для Copy elision, связанные с обработкой исключений.
§12.8/31
Еще одно допустимое условие — вызов копирующего/move конструктора может опускаться, если объект не связан ссылкой в коде).
Кроме того, С++11 позволяет избегать и move-конструктора + добавляется еще два условия для Copy elision, связанные с обработкой исключений.
§12.8/31
Я думаю, что для таких любителей оптимизаций вполне подойдет boost::ref / std::ref.
Спасибо, любопытно. Примеры на сайте STLXX кажутся весьма многословными и не очень интуитивными. Интересно было бы посмотреть на код делающий то же, что в статье (последний листинг).
Не совсем то, что вы спрашиваете, но близко.
algo2.iti.kit.edu/dementiev/stxxl/doxy/html/algo_2sort__file_8cpp-example.html
algo2.iti.kit.edu/dementiev/stxxl/doxy/html/algo_2sort__file_8cpp-example.html
Спасибо, да, вот на это я как раз посмотрел. Что пугает: определение 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; но для обучения — самое то ИМХО.
Так с обычным 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, который делал бы аналогичное тому, что приведено в последнем листинге.
См. www.boost.org/doc/libs/1_47_0/libs/serialization/doc/index.html -> Tutorial.
Основная идея — сначала сериализуем в контейнер, а затем проверяем. Можно, конечно, заявить, что это не совсем то. Но на самом деле как правило нужна именно сериализация, т.к. операции в памяти делаются гораздо быстрее, чем в файле. Предположим, что надо отсортировать данные файла, которые представляют собой int. Мне сложно представить, как это делается указанным выше способом. В boost:serialization это делается просто.
> сначала сериализуем в контейнер
Наверное, вы хотели сказать «десериализуем». В принципе, вы правильно поняли, что
> это не совсем то
Зачем мне какой-то дополнительный контейнер, если я хочу посчитать сумму, найти максимум, создать новый файл, который будет содержать только чётные числа из старого? Зачем мне дополнительная сущность в виде контейнера при решении каждой этой задачи? Бритва Оккамма решает…
Наверное, вы хотели сказать «десериализуем». В принципе, вы правильно поняли, что
> это не совсем то
Зачем мне какой-то дополнительный контейнер, если я хочу посчитать сумму, найти максимум, создать новый файл, который будет содержать только чётные числа из старого? Зачем мне дополнительная сущность в виде контейнера при решении каждой этой задачи? Бритва Оккамма решает…
А если надо отсортировать числа, то что тогда? Этот способ работает только в очень ограниченных случаях:
1. Возможно использование только Forward Iterators. Другие, такие как Reverse Iterator, Bidirectional, Random нельзя.
2. Контейнер содержит только old-plain data. Это тоже очень сильное ограничение, никакие классы, ООП и проч. нельзя использовать.
3. При всей кажущейся оптимальности, данный способ будет проходить по всем элементам, невзирая на то, что можно остановиться на первом элементе. Если же сделать реализацию, чтобы он останавливался, то тогда возникает следующая сложность: а что, если у нас контейнер содержит несколько контейнеров? Что тогда? Как перейти от первого ко второму?
Да, для частного использования частного вида задач это и можно использовать. Но для общего случая этот способ не очень подходит. Поэтому хотелось бы видеть не только идею, но и анализ: когда можно использовать, когда нельзя, какие ограничения. А также в чем выигрыш по сравнению с десериализацией и проверкой в контенере.
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.
Конечно, извращаться — это интересно. Но (исключительно примеряя себя) гораздо лучше понимается простая запись и чтение структур: 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, так что в данной теме это также оффтоп (от чего я и предостерегал в заключении статьи), хотя этому тоже, конечно, надо учить.
mdf-i.blogspot.com/2011/02/boostserialization-hell.html
Рекомендую прочитать данный пост, чтобы посмотреть на картину с «промышленными сериализаторами» иначе.
Рекомендую прочитать данный пост, чтобы посмотреть на картину с «промышленными сериализаторами» иначе.
В этом случае, конечно, static_cast не опасный, но по смыслу больше бы подошло то, что я называю implicit_cast:
Либо вообще добавить методы вроде get() которые возвращают T&, и тогда касты никакие не нужны, они только с толку сбивают.
Кстати, пример у меня работает и без operator T &(). Без operator T const &() const не компилируется, это понятно. А неконстантный вообще там нужен? (кроме, конечно, места, где делается static_cast к T&)
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: спасибо, хороший вопрос (наверное, добавлю в текст статьи). Именно преобразования нужны в примерах типа:
Эмм, 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);
Sign up to leave a comment.
Работа с бинарными файлами в стиле STL