Введение: Вечный вопрос "std::string в int"
Привет, коллега-программист! Если вы когда-нибудь писали на C++ парсер конфигов, обрабатывали пользовательский ввод или вытаскивали числа из CSV-файла, то наверняка сталкивались с задачей: «Как превратить обычную строку (std::string) в целое число (int)?». Казалось бы, что может быть проще? Однако, заглянув на Stack Overflow, вы увидите тысячи голосов и десятки вариантов ответов. Почему? Потому что за этим простым вопросом скрывается целый мир компромиссов между удобством, безопасностью и производительностью.
В этой статье мы не просто дадим сухой ответ. Мы устроим настоящий баттл между основными методами конвертации: std::stoi, std::from_chars (C++17), std::stringstream, старым добрым atoi и даже напишем свой велосипед. Вы узнаете, какой метод выбрать для высоконагруженного сервера, а какой — для быстрого прототипа. Поехали!
Метод 1: Классика — std::stoi (C++11)
Начиная с C++11, в стандартной библиотеке появилась элегантная функция std::stoi. Она проста, читаема и бросает исключения при ошибках. Это выбор 90% разработчиков для повседневных задач.
#include <iostream>
#include <string>
int main() {
std::string number_str = "12345";
try {
int value = std::stoi(number_str);
std::cout << "Число: " << value << std::endl;
} catch (const std::invalid_argument& e) {
std::cerr << "Ошибка: строка не содержит числа!" << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Ошибка: число выходит за пределы int!" << std::endl;
}
return 0;
}
Плюсы и минусы std::stoi
- Плюсы: Простота, поддержка локалей (распознает запятые как разделители), автоматическая обработка ошибок через исключения.
- Минусы: Исключения — это дорого. Если вы парсите миллионы чисел в цикле,
try-catchможет существенно замедлить код.
Метод 2: Быстрее ветра — std::from_chars (C++17)
Если вы пишете high-load приложение, где каждая наносекунда на счету, то std::from_chars — ваш герой. Эта функция не выделяет память, не бросает исключения и не зависит от локали. Она просто преобразует символы в число и возвращает указатель на место остановки.
#include <iostream>
#include <charconv>
#include <string>
#include <system_error>
int main() {
std::string number_str = "98765";
int value = 0;
auto [ptr, ec] = std::from_chars(number_str.data(),
number_str.data() + number_str.size(),
value);
if (ec == std::errc()) {
std::cout << "Число: " << value << std::endl;
} else {
std::cerr << "Ошибка конвертации!" << std::endl;
}
return 0;
}
Почему from_chars — король производительности?
- Zero-overhead: Никаких исключений, никаких скрытых аллокаций.
- Локализация отключена: Не нужно тратить время на проверку десятичных разделителей.
- Возвращает остаток строки: Идеально для парсинга колонок в CSV.
По бенчмаркам, from_chars может быть в 2-10 раз быстрее, чем stoi, особенно на коротких строках.
Метод 3: Тяжелая артиллерия — std::stringstream
Многие начинающие программисты используют std::stringstream для конвертации. Это мощный инструмент, но для задачи "строка в число" он избыточен, как танк для поездки в магазин за хлебом.
#include <sstream>
#include <string>
#include <iostream>
int main() {
std::string number_str = "42";
std::stringstream ss(number_str);
int value;
if (ss >> value) {
std::cout << "Число: " << value << std::endl;
} else {
std::cerr << "Ошибка!" << std::endl;
}
return 0;
}
Когда использовать stringstream?
Этот метод оправдан, если вам нужно одновременно парсить несколько типов данных из одной строки (например, "123 45.6 hello"). В остальных случаях он медленный и громоздкий.
Метод 4: Дедовский способ — atoi (из C)
Функция atoi из языка C живет в <cstdlib>. Она быстра, но крайне опасна: при ошибке возвращает 0, и вы не сможете отличить "0" от "abc".
#include <cstdlib>
#include <string>
#include <iostream>
int main() {
std::string number_str = "123";
int value = std::atoi(number_str.c_str());
std::cout << "Число: " << value << std::endl; // Но если строка "xyz", value будет 0
return 0;
}
Вердикт: Используйте только в легаси-коде или когда абсолютно уверены во входных данных. Для нового кода — забудьте.
Метод 5: Свой велосипед — ручной парсинг
Иногда для экстремальной оптимизации пишут собственный парсер. Например, если вы знаете, что строка всегда содержит только цифры и не длиннее 10 символов.
#include <string>
#include <cctype>
int fast_string_to_int(const std::string& str) {
int result = 0;
bool negative = false;
size_t i = 0;
if (str[i] == '-') {
negative = true;
++i;
}
for (; i < str.size(); ++i) {
if (!std::isdigit(str[i])) {
throw std::invalid_argument("Invalid character");
}
result = result * 10 + (str[i] - '0');
}
return negative ? -result : result;
}
Такой код может быть быстрее from_chars в некоторых синтетических тестах, но он не обрабатывает переполнение и менее универсален.
Битва бенчмарков: Кто быстрее?
Да