До C++17 существовало множество методов для преобразования целых чисел, чисел с плавающей запятой и двойных чисел в строки и обратно. Например, для выполнения этих задач можно было использовать std::stringstream, std::to_string, std::atoi, std::stoi и другие. На что существует множество сообщений, в которых обсуждаются различия между этими методами.
Однако в C++ 17 появились std::from_chars и std::to_chars. На что я хотел бы знать причины введения другого средства преобразования в строки и из строк.
Во-первых, какие преимущества и функциональные возможности обеспечивают эти новые функции по сравнению с предыдущими методами?
Кроме того, есть ли какие-либо заметные недостатки у этого нового метода преобразования строк?
Из источника заметок «... В отличие от других функций синтаксического анализа в библиотеках C++ и C, std::from_chars не зависит от языкового стандарта, не выделяет и не выбрасывает....": en.cppreference.com/w/cpp/utility/from_chars
Одно слово: СКОРОСТЬ!!!!
Вот если бы только GCC и Clang закончили их реализацию!





Все эти ранее существовавшие методы должны были работать на основе так называемой локали. Локаль — это в основном набор параметров форматирования, которые определяют, например, какие символы считаются цифрами, какой символ использовать для десятичной точки, какой разделитель тысяч использовать и так далее. Однако очень часто вам это не нужно. Если вы просто, например, читаете файл JSON, вы знаете, что данные отформатированы определенным образом, нет причин искать, должна ли '.' быть десятичной точкой или нет каждый раз, когда вы ее видите. Новые функции, представленные в <charconv>, в основном жестко закодированы для чтения и записи чисел на основе форматирования, установленного для локали C по умолчанию. Нет возможности изменить форматирование, но поскольку форматирование не обязательно должно быть гибким, оно может быть очень быстрым…
std::stringstream — чемпион в тяжелом весе. Он принимает во внимание такие вещи, как пропитанная локаль потока, и его функциональность включает в себя такие вещи, как создание сторожевого объекта на время форматированной операции, чтобы иметь дело с проблемами, связанными с исключениями. Операции форматированного ввода и вывода в библиотеках C++ имеют репутацию быть тяжеловесным и медленным.
std::to_string менее интенсивен, чем std::istringstream, но все же возвращает std::string, конструкция которого, вероятно, включает динамическое распределение (менее вероятно с современными методами оптимизации коротких строк, но все же вероятно). И в большинстве случаев компилятору по-прежнему необходимо генерировать все многословие на месте вызова для поддержки объекта std::string, включая его деструктор.
std::to_chars спроектированы так, чтобы занимать как можно меньше места. Вы предоставляете буфер, и std::to_chars делает очень мало, кроме фактического форматирования числового значения в буфер, в определенном формате, без каких-либо соображений, специфичных для локали, с единственными накладными расходами на обеспечение достаточного размера буфера. Код, использующий std::to_chars, не требует динамического распределения.
std::to_chars также немного более гибок с точки зрения параметров форматирования, особенно со значениями с плавающей запятой. std::to_string не имеет параметров форматирования.
std::from_chars также является легковесным синтаксическим анализатором, которому не нужно выполнять какое-либо динамическое распределение и не нужно жертвовать электронами для решения проблем локали или накладных расходов на потоковые операции.
std::to_string также учитывает текущую локаль. Так что я бы сказал, что он не менее, а на самом деле более гибкий в плане форматирования…
to/from_chars разработаны как элементарные функции преобразования строк. У них есть два основных преимущества перед альтернативами.
Они намного легче по весу. Они никогда не выделяют память (вы выделяете память для них). Они никогда не бросают исключений. Они также никогда не смотрят на локаль, что также повышает производительность.
По сути, они разработаны таким образом, что на уровне API невозможно иметь более быстрые функции преобразования.
Эти функции могут быть даже constexpr (это не так, хотя я не уверен, почему), в то время как более тяжелые версии с выделением и/или выбрасыванием не могут.
У них есть явные гарантии возврата. Если вы преобразуете float/double в строку (без указанной точности), реализация будет требуется, чтобы сделать так, чтобы взятие этой точной последовательности символов и преобразование ее обратно в float/double давало значение бинарно-идентичный. Вы не получите эту гарантию от snprintf, stringstream или to_string/stof.
Однако эта гарантия хороша только в том случае, если вызовы to_chars и from_chars используют одну и ту же реализацию. Таким образом, вы не можете ожидать отправки строки через Интернет на какой-либо другой компьютер, который может быть скомпилирован с другой реализацией стандартной библиотеки и получить то же самое float. Но это дает вам гарантии сериализации на компьютере.
Почему бы to/from_chars не иметь гарантий приема-передачи для разных реализаций? Если мы предположим, что ieee754, получим ли мы тогда гарантию приема-передачи между реализациями?
@Justin: Даже если вы предполагаете IEEE (который является стандартным не), вам все равно придется точно указать, как будет работать округление. Что может помешать реализации более высокой производительности для конкретной части оборудования.
Это свойство преобразования round_trip — то, что нам было нужно в течение многих лет. Только наличие гарантий для работы с собственными результатами реализации немного угнетает, когда вы работаете с обменом XML или JSON между строками. Я профилировал from_chars, и это было примерно в 10 раз быстрее, чем при использовании Boost lexical_cast с классическим набором локали каждый раз (поскольку некоторые библиотеки изменяют его без восстановления и тем самым уничтожают любое независимое от локали хранилище).
@ gast128: Дело в том, что вы всегда можете извлечь конкретную реализацию в свое приложение (или использовать общедоступную), которая даст вам все преимущества производительности при сохранении того же API.
Я думаю, что они различаются тем, как они обрабатывают локали, распределение памяти и поведение исключений, но у меня нет под рукой подробностей.