1 апр. 2009 г.

Использование boost::lexical_cast

Каждому в своей программисткой практике приходилось сталкиваться с ситуациями, когда необходимо было что-либо преобразовать из строки в, предположим, число, или обратно. В зависимости от того, что, куда и как преобразовывается, для этого можно применять sprintf/sscanf, _itoa/atoi (и другие функции из этой группы), strtoi, и т. п. Все эти методы по своему хороши, но для каждого варианта преобразования необходимо помнить - «а что лучше всего для этого преобразования подходит?»

Стандарт языка C/C++ предусматривает ряд средств для выполнения таких преобразований. Однако они различаются по простоте использования, расширяемости и безопасности.

К примеру, существует ряд ограничений для стандартной для языка C функции atoi:

  • Преобразование выполняется только в одном направлении: из текстовой формы во внутренний тип данных. Обратное преобразование с использованием стандартных функций C требует либо использования неудобной и не совсем безопасной функции sprintf, или потери платформонезависимости при использовании таких функций, как itoa.
  • Набор поддерживаемых типов является лишь подмножеством встроенных числовых типов, а именно int, long, и double.
  • Этот набор типов не может быть расширен однородным образом. К примеру, преобразование из строкового представления в complex или rational.

Стандартная функция C strtol имеет те же же основные ограничения, хотя предлагает более аккуратный контроль за процессом преобразования. Однако, в общем случае такой контроль или не требуется, или не используется. Семейство функций scanf предоставляет еще больший контроль, однако им не хватает надежности и простоты использования.

Стандартная библиотека языка C++ содержит stringstream как возможную основу для обсуждаемого форматирования с преобразованием. Она предлагает удобный набор средств для контроля за форматированием и преобразованием I/O из и в произвольный тип через текст. Для простых преобразований непосредственное использование stringstream может быть неудобным (или введения дополнительных локальных переменных и невозможности использования удобной инфиксной формы выражений) или неочевидным и туманным (когда объекты типа stringstream создаются как временные объекты в выражении). Локаль (facets) содержит приемлемый набор средств для контроля за представлением в текстовом виде, однако сложность кода приводит к тому, что использовать эти средства удобно только небольшму количеству программистов.

Шаблон функции lexical_cast предлагает удобный и целостный подход для поддержки распространенных преобразований в и из произвольных типов, когда они представлены в виде текста. Предлагаемое ей упрощение заключается в использовании удобстве применения в выражениях. Для более сложных преобразований, когда, к примеру, требуется контролировать точность, вместо lexical_cast лучше использовать удобный подход  stringstream. Для преобразования одного числа в другое вместо lexical_cast рекомендуется использовать numeric_cast.

Детальное обсуждение всех опций и вопросов, возникающих при форматировании строк, включая сравнение stringstream, lexical_cast, и других средств, можно найти в статье Херба Саттера (Herb Sutter)  The String Formatters of Manor Farm.

boost::lexical_cast позволяет снять эту проблему. Для его использования достаточно знать - какой тип в какой вы ходите преобразовать. И все. Например:

// преобразует целое в строку
std::string str = boost::lexical_cast<std::string>(10);

// строку в целое
int value = boost::lexical_cast<int>("10");
// преобразует пару целых в экземпляр класса Point
Point pt = boost::lexical_cast<Point>("10, 20");

// обратное преобразование
std::wstring wstr = boost::lexical_cast<std::wstring>(pt);

// и т. д.

Достаточно удобно, не правда ли?

Принципы работы.

Принцип работы boost::lexical_cast очень прост. Для преобразования он использует строковый поток (std::strstream), выводя (с помощью оператора «) в него преобразуемое значние, после чего читая из него значение типа, в который делается преобразование. Т. е. вызов boost::lexical_cast эквивалентен:

// положим, что from_val и to_val - это, соответственно преобразуемое

// значение и приемник результата преобразования
std::ostringstream o_str;
o_str << from_val
std::istringstream i_str(o_str.str());
i_str >> to_val;

Из этого становится очевидным, что для корректного выполнения преобразования необходимо (и достаточно) наличия для преобразуемого типа перегруженного оператора вывода в поток и/или чтения из потока. Для всех примитивных типов такие операторы реализованы в библиотеке STL, а для «пользовательских» типов такой оператор может написать сам разработчик.

Краткое описание

Определенные в хидере "boost/lexical_cast.hpp" части библиотеки:

namespace boost
{
    class bad_lexical_cast;
    template <typename Target, typename Source>
      Target lexical_cast(Source arg);
}

Тестовая программа - в файле lexical_cast_test.cpp.

lexical_cast

template <typename Target, typename Source>
  Target lexical_cast(Source arg);

Функция возвращает результат передачи значение arg в стандартный строковый поток и обратного преобразования в объект типа Target. Когда тип Target это std::string или std::wstring, извлечение из потока получает полное содержимое строки, включая пробелы, вместо того, чтобы использовать operator>> по умолчанию. Если преобразование дает ошибку, то генерируется исключение bad_lexical_cast.

Требования к аргументу и возвращаемому значению:

  • Source является OutputStreamable, что значает, что определен operator<<, который получает объект типа std::ostream или std::wostream слева и объект типа аргумента справа.

  • Target является InputStreamable, что означает, что определен operator>>, который берет объект типа std::istream или std::wistream слева (left hand side) и объект возвращаемого типа справа.

  • Source и Target следуют парадигме CopyConstructible [20.1.3].

  • Target следует парадигме DefaultConstructible, что означает, что возможна инифиализация по умолчанию объекта этого типа [8.5, 20.1.4].

Символьный тип используемого потока должен быть char, если только Source или Target не требуют использования потоков с wchar_t. Типы Source, которые требую использования wchar_t-потоков это wchar_t, wchar_t *, и std::wstring. Типы Target, которые требуют использования wchar_t-потоков это wchar_t и std::wstring.

Если требуется более высокий уровень контроля за преобразованием, лучше использовать std::stringstream и std::wstringstream. Когда требуется выполнить преобразования без использования потоков, lexical_cast является неподходящим средством.

bad_lexical_cast

class bad_lexical_cast : public std::bad_cast
{
{
public:
    ... // same member function interface as std::exception
};

Для сообщений об ошибках преобразования в lexical_cast используется генерация исключений.

Недостатки

Но не смотря на свою красоту, универсальность и соответствие общепринятым стандартам преобразования, у boost::lexical_cast есть и следущие недостатки:

  • Низкая скорость работы. По тестам, проведенным Гербом Саттером, результаты которых он описал в своих «Новых сложных задачах на С++», boost::lexical_cast работает на порядок медленнее, чем тот же sprintf.
  • При преобразованиях нельзя специфицировать формат желаемой или исходной строки. Т. е. преобразования выполняются в соответствии со стандартными настройками потоков.
  • (для компилятора Visual C++) нельзя (без дополнительных телодвижений) выполнять преобразования в std::wstring/wchar_t*, если в настройках проекта не указано, что wchar_t считается встроенным типом.
  • приведение в тип double из строкового типа, возможно лишь в том случае если в качестве разделителя (дробной части от целой) используется точка.

Достоинства

Два из трех указанных выше недостатков достаточно легко обходятся. Поскольку boost::lexical_cast - это шаблон, то достаточно несложно написать необходимую специализацию, выполняющую преобразование настолько быстро, насколько это необходимо разработчику, а также учитывающую особенности типа wchar_t в VC++.

Изменения

  • Предыдущая версия шаблона lexical_cast использовала установки точности в потоках по умолчанию для чтения и записи чисел с плавающей запятой. Для числовых типов есть соответствующая специализация в std::numeric_limits, а текущая версия шаблона преобразования соответствующую точность.
  • Предыдущая версия шаблона lexical_cast не поддерживала преобразование в или из типов на основе wchar_t. Для компиляторов, которые имеют полную поддержку для wchar_t, lexical_cast теперь поддерживает преобразование из wchar_t, wchar_t *, и std::wstring и в wchar_t и std::wstring.
  • Предыдущая версия шаблона exical_cast основывалась на предположении, что обычные операторы извлечения из потока (stream extractor operators) достаточны для чтения значений. Однако, строковый ввод/вывод асимметричен, так как пробелы играют роль разделителей, а не входят в состав строк. Текущая версия исправляет эту ошибку для std::string и, где возможно, std::wstring: lexical_cast<std::string>("Hello, World") выполняется успешно вместо ошибки с генерацией исключения bad_lexical_cast.
  • Предыдущая версия шаблона lexical_cast допускала небезопасное и бессмысленное преобразование к указателям. Текущая версия генерирует исключение bad_lexical_cast для преобразований в указатель: lexical_cast<char*>("Goodbye, World") генерирует исключение вместо неопределенного поведения в предыдущей версии.

Пример использования.

Следующий пример преобразует аргументы командной строки в последовательность чисел:

int main(int argc, char * argv[])
{
    using boost::lexical_cast;
    using boost::bad_lexical_cast;

    std::vector<short> args;

    while(*++argv)
    {
        try
        {
            args.push_back(lexical_cast<short>(*argv));
        }
        catch(bad_lexical_cast &)
        {
            args.push_back(0);
        }
    }
    ...
}

Приведенный далее пример использует числовые величины в строковой выражении:

void log_message(const std::string &);

void log_errno(int yoko)
{
    log_message("Error " + boost::lexical_cast<std::string>(yoko) + ": " + strerror(yoko));
}

Приведенный далее пример переводит числовые величины в строковое представление:

#include <string>
#include <iostream>
#include <boost/lexical_cast.hpp>

int _tmain(int argc, _TCHAR* argv[])
{
    try {
        int a(41);
        std::string b;
        b = boost::lexical_cast<std::string>(a);
        std::cout << b << std::endl;
    }
    catch (const std::exception& exc)
    {
        std::cout << exc.what() << std::endl;
    }
    return 0;
}

P.S.: Нагло спёрто отсюда [[doc:cpp:boost:lexical_cast]] и отсюда библиотека преобразований - хидер boost/lexical_cast.hpp

P.P.S.: Об эффективности boost::lexical_cast<> можно прочитать здесь: http://www.rsdn.ru/forum/flame.comp/2986431.flat.1.aspx

7 комментариев:

  1. Спасибо, отличная статья, помогла новичку С++ понять сходу, что за зверь этот boost::lexical_cast

    ОтветитьУдалить
  2. И через три года эта статья хороша, почему новых нет?

    ОтветитьУдалить
  3. Херня програмист програмист програмист все код пишет а хакнули комп и унесли байты все бля ищи на хуй теперь

    ОтветитьУдалить
  4. #include
    using std::string;
    #define _my_ntostring(n) string(#n,sizeof(#n)-1)
    inline string my_itostring(int n) {(void)n;return _my_ntostring(n);}

    ОтветитьУдалить
    Ответы
    1. Неужели сам придумал? Или это из теста на знание плюсов?
      my_itostring() будет всегда выдавать строку "n", обломись.

      Удалить
  5. Grand Casino Hotel, Tunica - Mapyro
    Grand Casino 아산 출장마사지 Hotel, Tunica is a hotel and 원주 출장샵 casino located in Tunica Resorts, 천안 출장마사지 Mississippi, United States and is open 제천 출장샵 daily 24 hours. Rating: 7.1/10 · ‎2,821 충청북도 출장마사지 votes · ‎Price range: $$

    ОтветитьУдалить