2 апр. 2009 г.

Организация доступа к 32-bit DLL из 64-bit приложения.

Перенос 32-битных Windows приложений на 64-битную машину может быть весьма проблематичным, если у вас есть 32-разрядные библиотеки DLL, которые вы не можете переписать и портировать для 64-bit. Майк Беккер покажет вам, как можно получить доступ к 32-bit DLL из 64-битного кода с помощью встроенных механизмов IPC.

64-разрядные технологии Microsoft впервые появились в Windows Server 2003 для процессоров Itanium 2 (архитектура ихвестна как "IA64") а также для eXtended CPUs (архитектура известна как "x64"). 64-bit технология имеет много преимуществ, но также поднимает новые вопросы для разработчиков программного обеспечения. Например, вам может понадобиться необходимость доступа к существующим 32-разрядным библиотекам DLL из 64-битного процесса.

Главное преимущество технологии 64-бит состоит в способности адресовать до 8 ТБ памяти, против максимальных 2 Гб для 32-битных процессов. Как результат, 64-разрядная технология позволяет проводить операции с большим объёмом данных в оперативной памяти без необходимости временного сброса памяти на жётский диск. Это может значительно повысить производительность и открыть новые алгоритмы обработки данных базирующихся на очень больших доступных объёмов оперативной памяти. Как бы то ни было аргументы для миграции существующего программного обеспечения на 64-битную платформу имеют место быть.

Многие приложения, написанные с помощью C/C++ могут быть легко портированы под 64-битную платформу, особенно если они написаны в виде монолитного модуля. Иногда достаточно просто пересобрать исходные коды с использованием x64/IA64 компилятора. Однако уже опубликованное или базирующееся на модулях ПО может вызвать проблемы.

Конфликт: 64-bit против 32-bit

Основная проблема миграции возникает при необходимости портирования 32-разрядных программных компонентов, которые не могут быть пересобраны, возможно, потому что исходный код потерян, к нему нет доступа или одна из зависимостей этого модуля не можeт быть перенесена на 64-bit платформу.

32-битное ПО по-прежнему поддерживается на 64-битной платформе. Так 32-битные процессы могут выполняться внутри Windows WOW64-подсистемы, которая является частью всех 64-битных систем Windows. Однако 64-разрядный процесс не может загружать 32-разрядные модули в своё адресное пространство, также и 32-разрядные процессы не могут загружать 64-разрядные модули в своё адресное пространство. Единственный способ общения между 32-битными и 64-битными модулями возможен путём межпроцессного взаимодействия (IPC). Другими словами, 32-разрядные и 64-разрядные процессы могут обмениваться данными с использованием IPC-механизмов, например такие как out-of-proc COM, сокеты, сообщения Windows или MMF (Memory mapped files).

Например, 32-битный программный продукт содержит основной модуль WeatherReport (см. рисунок выше), который внутри обращается к DLL WeatherStationControl. Пока основной модуль и DLL являются 32-разрядными, продукт может работать как на 32-битных, так и 64-битных платформах (внутри WOW64). Если основной модуль и DLL переносятся на 64-битную платформу, то они могут работать в рамках 64-битных процессов. Однако, если только основной модуль переносится на 64-bit, он не сможет загружать 32-разрядные DLL.

Лучшим способом переноса такого продукта на 64-битную платформу является миграция как основного модуля, так и всех его зависимостей, но если хоть одна зависимая DLL не может быть перенесена, то продукт не может быть загружен в 64-битном процессе, следовательно приложение работать не будет.

Решение: суррогатный процесс

Эта проблема может быть решена путем загрузки зависимой 32-битной DLL в отдельном пространстве 32-разрядного процесса. Основной модуль, работая в качестве 64-битного процесса, может получить доступ к зависимым DLL'ям через границы процессов, используя IPC (Смотри MSDN reference).

64-битный процесс может получить доступ к 32-разрядной DLL используя механизмы межпроцессного взаимодействия. То бишь 32-разрядные DLL загружаются в отдельный 32-битный суррогатный процесс, и тогда 64-битное приложение использует встроенные механизмы IPC для того, чтобы обмениваться данными 32-битным процессом.

Это решение требует дополнительной работы, например необходимо создать 32-разрядный суррогатный процесс, который загрузит 32-разрядные DLL и предоставит API для работы с ними. Кроме того, некоторые изменения будет необходимо стелать со стороны 64-битного клиента, т.к. клиент будет вынужден использовать методы IPC вместо непосредственного доступа к 32-разрядной DLL. Стоит отметить, что, в крайнем случае, эта дополнительная работа может быть сравнима с работой, которую необходимо выполнить при разработке 64-разрядной версию 32-битной DLL с нуля.

Одним из возможных путей сокращения этих расходов является реализация 64-разрядной DLL обертки, которая предоставит те же функции, что и оригинальная 32-битная DLL. Эта обёртка уже внутри себя скроет детали IPC вызовов оригинальной 32-битной DLL, загруженной в 32-битный суррогатный процесс.

64-битная обёртка (WeatherStationControl64.DLL) экспортирует тот же интерфейс, что и оригинальная 32-битная DLL (WeatherStationControl.DLL), то бишь предоставляет главному модулю WeatherReport те же сервисы без необходимости внесения изменений в код модуля WeatherReport.

Основные затраты этого решения связаны с реализацией суррогатного процесса, загружающего 32-битную DLL и реализаццей 64-битной DLL оболочки. Фактически затраты будут зависеть от того, какая из IPC технологий будет использоваться для обмена данными между 64-битным и 32-битным процессами.

Использование COM для межпроцессного взаимодействия

Один из самых популярных методов IPC - это DCOM (Distributed COM). Первоначально разработанная для распределенных систем, DCOM по-прежнему поддерживается как на 64-битных платформах Windows. Модули COM могут быть собраны как 32-разрядные так и 64-разрядные. Единственным ограничением является то, что 64-битные и 32-битные модули не могут находиться в одном и том же процессе, следовательно они должны взаимодействовать через границы процессов. Это делается с помощью out-of-process (OOP) COM компонентов, следующим образом:

  1. Создаём 32-битный COM-Сервер, который загрузит 32-битную DLL и опубликует 32-bit DLL интерфейс как COM-интерфейс, реализованный внутри через делегирование к API исходной 32-битной DLL.
  2. Конфигурируем этот COM-Сервер для out-of-proc загрузки любым способом создания COM+ приложений (используя dllhost.exe в качестве суррогата).
    Также можно реализовать этот COM-компонент как специальный COM-Сервер EXE, используя ATL COM Server в качестве хостящего процесса.
    Можно также помеcтить этот COM-компонент внутрь Win32-сервиса.
  3. Создаём 64-разрядную DLL оболочку, реализующую тот же интерфейс, как оригинальная 32-битная DLL. Обёртка будет импортировать COM-интерфейс из COM-объекта, созданного выше, и транслировать каждый API вызов в обращение к COM-интерфейсу, передавая параметры вызова и получая возвращаемые значения.

32-разрядная DLL (WeatherStationControl.DLL) используется СОМ-объектом (WeatherStationWrapper), который предоставляет интерфейс 32-битной DLL в качестве COM-интерфейса. 64-битная DLL обёртка (WeatherStationControl64.DLL) делает вызовы COM-интерфейса, которые уже делегируют всю работу оригинальным API вызовам исходной 32-битой DLL. Основной процесс (WeatherReport) использует интерфейс, предоставляемый 64-разрядной DLL обёрткой, но на самом деле работа выполняется в оригинальной 32-битной DLL.

Это решение должно быть значительно дешевле, чем создание 64-разрядную версию 32-битных DLL с нуля. Библиотека ATL, поддерживающаяся Visual Studio, вкупе со всеми своими визардами и готовыми фрагментами кода, также должны помочь снизить затраты миграции за счет экономии времени и снижения вероятности ошибок.

Последствия

Однако существует ряд вещей, которые вам все еще нужно иметь в виду:

  1. Выравнивание
    Выравнивание данных в памяти отличается в 32-bit и 64-bit процессах. Это означает, что более сложные пользовательские структуры данных могут быть сериализованы 32-разрядным процессом иначе, чем ожидается в 64-битном проыцессе, и наоборот. Microsoft Windows Platform SDK включает документацию о различиях в соответствие памяти данными между 32-битной и 64-битных процессов.
  2. Типы данных
    В большинстве случаев, 64-разрядная Windows использует те же типы данных, как 32-битной версии. Различия в основном в указателях, которые 32-битные в 32-битной версий Windows и 64-битные в 64-битных Windows.
    Указатель полученных данных типов, таких как HANDLE и HWND также различны между 32-битной и 64-разрядных версиях. Windows позволяет вести единый базовый код для 32-разрядных версиях и 64-битного программного обеспечения, предлагая полиморфные типы данных, которые имеют различную длину в зависимости от целевой платформы, например INT_PTR представляет целое с таким же размером, как и указатель. Любая переменная этого типа будет целым числом, которое составляет 32 бита на 32-битной платформе и 64 бита на 64-битной платформе.
  3. COM инициализации
    Вы можете получить доступ к СОМ-объекту, если он был успешно инициализирован. Функция COM API CoInitialize() должна вызываться для каждого потока, который собирается работать с COM-объектом, т.е. делать вызовы COM-интерфейсов, также должна вызываться CoUninitialize() перед завершением потока.(см. MSDN). Это правило должно строго соблюдаться, если основной процесс вызывает оригинальные 32-битные DLL из разных потоков выполнения.
  4. Безопасность
    При использовании out-of-proc, экземпляры COM-объектов находятся в отдельном процессе, независимо от того используете вы стандартный суррогатной процесс, EXE COM-Сервер или Win32 сервис. Это может означать, что вызовы 32-битной DLL могут произойти в другом контексте безопасности, чем у основного процесса, особенно если основной процесс интенсивно использует имперсонирование. Если это так, вы можете настроить собственные параметры доступа для out-of-proc компонента, или осуществлять внутреннее имперсонирование внутри COM-объекта.
  5. Производительность
    IPC-решение почти наверняка будет медленнее, чем прямые вызовы DLL. Маршалинг данных за границы процессов, автоматическое преобразование данных между 32 и 64 бит, WOW64 особенности, задержки инстанцирования экземпляров COM-объектов - всё это будет влиять на производительность. Однако есть много методов оптимизации, которые можно использовать, таких как COM pooling, кэширование данных внутри DLL-оболочки, реализация критичных к производительности интерфейсов в 64-битной DLL, и так далее.
  6. Перенаправление
    Подсистема WOW64 отвечает за поддержку 32-битных модулей на 64-битных Windows. Чтобы избежать нежелательных коллизий между 32-битным и 64-битным ПО, особенно при доступе к файловой системы и реестру, WOW64 изолирует 32-разрядные модули, используя механизм, называемый перенаправление (см. MSDN).
    Например, для 64-битного процесса при получении пути системной папки возвращается %WINDOWS%\System32, а для 32-разрядного процесса возвращается %WINDOWS%\SysWOW64.
    Путь к папке исполняемого файла будет "Program Files" для 64-битного процесса, но для 32-разрядного процесса это будет "Program Files (x86)".
    Раздел реестра HKEY_LOCAL_MACHINE\Software содержит настройки и данные для 64-разрядных процессов, а ключ HKEY_LOCAL_MACHINE\Software\WOW6432Node содержит настройки и данные для 32-разрядных процессов.
    Это перенаправление активируется автоматически, когда программные модули пытаются получить предопределённые системные пути или ключи реестра.
  7. Модули ядра
    Предложенное здесь решение работает для 32-разрядных DLL, использующихся в user mode, но не работает с 32-битными драйверами. Это происходит, потому что 32-битные модули ядра не могут быть использованы на 64-битной платформе, без исключений или обходных путей. Если ваш продукт включает в себя любой модуль уровня ядра, таких как драйвер устройства, то единственно возможный путь миграции - это портировать модуль ядра на 64-битную платформу.
  8. Установка.
    Использование out-of-proc COM-компонента требует изменений в процедуре установки вашего ПО, т.к. COM-компонент должен быть установлен и зарегистрирован в системе. Как уже говорилось выше в пункте Безопасность, для этого может потребоваться настройка специальных параметров доступа для COM-компонента.
Тыкните сюда дабы скачать пример кода

Оригинал статьи здесь: Mike Becker: "Accessing 32-bit DLLs from 64-bit code", Jun 2007

Литература:

1 апр. 2009 г.

Base64 Encoder

Велосипед отечественного производства для кодирования бинарных данных в текст с кодировкой Base64 и обратно.

Файл check_helpers.hpp
//------------------------------------------------------------------------
#ifndef __CHERCKHELPERS_HEADER_INCLUDED__
#define __CHERCKHELPERS_HEADER_INCLUDED__
//------------------------------------------------------------------------
#include <boost/type_traits.hpp>
//------------------------------------------------------------------------
template<class IterTypeT>
struct IsRandomAccesIterator
{
    static const bool value = false;
};
template<>
struct IsRandomAccesIterator<std::random_access_iterator_tag>
{
    static const bool value = true;
};
//------------------------------------------------------------------------
#define CHARTYPE_SOURCE_SEQUENCE_CHECK(stl_container_t) \
    BOOST_STATIC_ASSERT ( \
    boost::is_fundamental<typename stl_container_t::value_type>::value ); \
    BOOST_STATIC_ASSERT ( \
    sizeof(typename stl_container_t::value_type) == static_cast<std::size_t>(1) );
//------------------------------------------------------------------------
#define CHARTYPE_SOURCE_ITERATOR_CHECK(iterator_t) \
    BOOST_STATIC_ASSERT ( \
        boost::is_fundamental<typename std::iterator_traits<iterator_t>::value_type>::value ); \
    BOOST_STATIC_ASSERT ( \
        sizeof(typename std::iterator_traits<iterator_t>::value_type) == static_cast<std::size_t>(1) );
//------------------------------------------------------------------------
#define RANDOM_ACCESS_ITERATOR_CHECK(iterator_t) \
    BOOST_STATIC_ASSERT ( \
    IsRandomAccesIterator< \
        typename std::iterator_traits<iterator_t>::iterator_category>::value );
//------------------------------------------------------------------------
//------------------------------------------------------------------------
#endif // __CHERCKHELPERS_HEADER_INCLUDED__
//------------------------------------------------------------------------

Файл base64_conv.h
//------------------------------------------------------------------------
#ifndef __BASE64_CONVERTER_HEADER_INCLUDED__
#define __BASE64_CONVERTER_HEADER_INCLUDED__
//------------------------------------------------------------------------
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/type_traits.hpp>
#include <vector>
#include <string>
#include <iterator>
#include <algorithm>
#include "check_helpers.hpp"
//------------------------------------------------------------------------
class base64_conv : private boost::noncopyable
{
public:
    typedef std::vector<char>           buffer_t;
    typedef boost::shared_ptr<buffer_t> p_buffer_t;

public:
    base64_conv();
    ~base64_conv();

    template<typename stl_container_t>
    void to_base64 ( const stl_container_t & sourse_data )
    {
        // This code can not be compiled, because a sequence is contained
        // by a wrong type; need sequence which contained by predefined char type
        CHARTYPE_SOURCE_SEQUENCE_CHECK(stl_container_t);

        std::vector<typename stl_container_t::value_type> tmp_buffer (
            sourse_data.begin(), sourse_data.end() );
        to_base64(tmp_buffer);
    }
    
    template<typename char_t, typename allocator_t>
    void to_base64 (
        const std::vector<char_t, allocator_t> & sourse_data )
    {
        if ( sourse_data.empty() )
        {
            p_buffer->clear();
            return;
        }

        to_base64_for_vector_or_string ( sourse_data.begin(), sourse_data.end() );
    }

    template<typename char_t, typename char_traits_t, typename allocator_t>
    void to_base64(
        const std::basic_string<char_t, char_traits_t, allocator_t> & sourse_data )
    {
        if ( sourse_data.empty() )
        {
            p_buffer->clear();
            return;
        }

        to_base64_for_vector_or_string ( sourse_data.begin(), sourse_data.end() );
    }

    template<typename stl_container_t>
    void from_base64( const stl_container_t & sourse_data )
    {
        // This code can not be compiled, because a sequence is contained
        // by a wrong type; need sequence which contained by predefined char type
        CHARTYPE_SOURCE_SEQUENCE_CHECK(stl_container_t);

        std::vector<typename stl_container_t::value_type> tmp_buffer (
            sourse_data.begin(), sourse_data.end() );
        from_base64(tmp_buffer);
    }

    template<typename char_t, typename allocator_t>
    void from_base64 (
        const std::vector<char_t, allocator_t> & sourse_data )
    {
        if ( sourse_data.empty() )
        {
            p_buffer->clear();
            return;
        }

        from_base64_for_vector_or_string (
            sourse_data.begin(), sourse_data.end() );
    }

    template<typename char_t, typename char_traits_t, typename allocator_t>
    void from_base64 (
        const std::basic_string<char_t, char_traits_t, allocator_t> & sourse_data )
    {
        if ( sourse_data.empty() )
        {
            p_buffer->clear();
            return;
        }

        from_base64_for_vector_or_string (
            sourse_data.begin(), sourse_data.end() );
    }

    p_buffer_t get_result();
    void get_result ( std::string & out_str );
    void get_result ( std::vector<char> & out_buffer );

private:
    template<typename in_rait_t>
    void to_base64_for_vector_or_string (
        const in_rait_t it_begin, const in_rait_t it_end )
    {
        // This code can not be compiled, because a sequence is contained
        // by a wrong type; need sequence which contained by predefined char type
        CHARTYPE_SOURCE_ITERATOR_CHECK(in_rait_t);

        // This code can not be compiled, because iterator_category of in_rait_t
        // is not equal std::random_access_iterator_tag
        RANDOM_ACCESS_ITERATOR_CHECK(in_rait_t);

        std::size_t data_length = std::distance(it_begin, it_end);
        if ( data_length <= 0 )
        {
            p_buffer->clear();
            return;
        }

        const char * p_data_begin = reinterpret_cast<const char*>( & * it_begin );
        do_encode_to_base64 ( p_data_begin, data_length );
    }

    template<typename in_rait_t>
    void from_base64_for_vector_or_string (
        const in_rait_t it_begin, const in_rait_t it_end )
    {
        // This code can not be compiled, because a sequence is contained
        // by a wrong type; need sequence which contained by predefined char type
        CHARTYPE_SOURCE_ITERATOR_CHECK(in_rait_t);

        // This code can not be compiled, because iterator_category of in_rait_t
        // is not equal std::random_access_iterator_tag
        RANDOM_ACCESS_ITERATOR_CHECK(in_rait_t);

        std::size_t data_length = std::distance(it_begin, it_end);
        if ( data_length <= 0 )
        {
            p_buffer->clear();
            return;
        }

        const char * p_data_begin = reinterpret_cast<const char*>( & * it_begin );
        do_decode_from_base64 ( p_data_begin, data_length );
    }

    void do_encode_to_base64 (
        const char * sourse_data,
        std::size_t sourse_data_size );

    void do_decode_from_base64 (
        const char * sourse_data,
        std::size_t sourse_data_size );

private:
    enum { MAX_LINE_LENGTH = 72 };
    p_buffer_t p_buffer;
};
//------------------------------------------------------------------------
#endif // __BASE64_CONVERTER_HEADER_INCLUDED__
//------------------------------------------------------------------------

Файл base64_conv.cpp
//------------------------------------------------------------------------
#include "stdafx.h"
#include "base64_conv.h"
//------------------------------------------------------------------------
namespace {

// Translation Table as described in RFC1113
//------------------------------------------------------------------------
const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// Translation Table to decode (created by author)
//------------------------------------------------------------------------
const char cd64[]="|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq";

// encode 3 8-bit binary bytes as 4 '6-bit' characters
//------------------------------------------------------------------------
void encodeblock ( unsigned char in[3], unsigned char out[4], int len )
{
    out[0] = cb64[ in[0] >> 2 ];
    out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
    out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
    out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
}

// decode 4 '6-bit' characters into 3 8-bit binary bytes
//------------------------------------------------------------------------
void decodeblock( unsigned char in[4], unsigned char out[3] )
{   
    out[ 0 ] = (unsigned char ) (in[0] << 2 | in[1] >> 4);
    out[ 1 ] = (unsigned char ) (in[1] << 4 | in[2] >> 2);
    out[ 2 ] = (unsigned char ) (((in[2] << 6) & 0xc0) | in[3]);
}

// base64 encode a sequence adding padding and line breaks as per spec.
//------------------------------------------------------------------------
void encode (
    const char * in_sequence,
    std::size_t in_length,
    std::vector<char> & out_sequence,
    int line_size )
{
    unsigned char in[3], out[4];
    int i, len, blocks_out = 0;

    out_sequence.clear();
    out_sequence.reserve(in_length * 4/3);

    const char * it_begin   = in_sequence;
    const char * it_end     = in_sequence + in_length;

    while ( it_begin < it_end )
    {
        len = 0;
        for ( i = 0; i < 3; i++ )
        {
            if ( it_begin < it_end )
            {
                in[i] = static_cast<unsigned char>(*it_begin++);
                len++;
            }
            else
            {
                in[i] = 0;
            }
        }
        if ( len )
        {
            encodeblock( in, out, len );
            for( i = 0; i < 4; i++ )
            {
                out_sequence.push_back(out[i]);
            }
            blocks_out++;
        }
        if ( blocks_out >= (line_size/4) || ( it_begin == it_end ) )
        {
            if ( blocks_out )
            {
                out_sequence.push_back('\r');
                out_sequence.push_back('\n');
            }
            blocks_out = 0;
        }
    }
}

// decode a base64 encoded sequence discarding padding, line breaks and noise
//------------------------------------------------------------------------
void decode (
    const char * in_sequence,
    std::size_t in_length,
    std::vector<char> & out_sequence )
{
    unsigned char in[4], out[3], v;
    int i, len;

    out_sequence.clear();
    out_sequence.reserve(in_length * 3/4);

    const char * it_begin   = in_sequence;
    const char * it_end     = in_sequence + in_length;

    while( it_begin < it_end )
    {
        for( len = 0, i = 0; i < 4 && (it_begin < it_end); i++ )
        {
            v = 0;
            for ( ; (it_begin < it_end) && v == 0 ; ++it_begin )
            {
                v = static_cast<unsigned char>(*it_begin);
                v = static_cast<unsigned char>((v < 43 || v > 122) ? 0 : cd64[ v - 43 ]);
                if( v )
                {
                    v = static_cast<unsigned char>((v == '$') ? 0 : v - 61);
                }
            }
            if ( v )
            {
                len++;
                in[i] = static_cast<unsigned char>(v - 1);
            }
            else
            {
                in[i] = 0;
            }
        }
        if( len )
        {
            decodeblock( in, out );
            for( i = 0; i < len - 1; i++ )
            {
                out_sequence.push_back(out[i]);
            }
        }
    }
}

}
//------------------------------------------------------------------------
base64_conv::base64_conv()
    : p_buffer ( new std::vector<char> )
{
};
//------------------------------------------------------------------------
base64_conv::~base64_conv()
{
};
//------------------------------------------------------------------------
void base64_conv::do_encode_to_base64 (
    const char * sourse_data, std::size_t sourse_data_size )
{
    encode ( sourse_data, sourse_data_size, *p_buffer, MAX_LINE_LENGTH );
}
//------------------------------------------------------------------------
void base64_conv::do_decode_from_base64 (
    const char * sourse_data, std::size_t sourse_data_size )
{
    decode ( sourse_data, sourse_data_size, *p_buffer );
}
//------------------------------------------------------------------------
base64_conv::p_buffer_t base64_conv::get_result()
{
    return p_buffer;
}
//------------------------------------------------------------------------
void base64_conv::get_result (
    std::string & out_str )
{
    std::string ret_val;
    ret_val.reserve(p_buffer->size());
    std::copy (
        p_buffer->begin(),
        p_buffer->end(),
        std::back_inserter(ret_val) );
    std::swap(out_str, ret_val);
}
//------------------------------------------------------------------------
void base64_conv::get_result (
    std::vector<char> & out_buffer )
{
    std::swap(out_buffer, *p_buffer);
    p_buffer->clear();
}
//------------------------------------------------------------------------

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

В данной статье я расскажу как написать свой парсер ini-файлов на C++. За основу возьмём контекстно-свободную грамматику, построенную в предыдущей статье "Создаём парсер для ini-файлов. Теория". Для построения парсера будет использоваться библиотека Boost Spirit, которая позволяет строить свои собственные парсеры комбинируя готовые примитивные парсеры при помощи парсерных комбинаторов.

Важно: в данной статье предполагается, что читатель знаком с основами C++ (в том числе будет активно использоваться STL). Если вы не очень в себе уверены, то я советую сначала прочитать пару статей для новичков по С++ и по STL.

Грамматика


Для начала вспомним какую грамматику для ini-фалов мы построили в предыдущей статье:
inidata = spaces, {section} .
section = "[", ident, "]", stringSpaces, "\n", {entry} .
entry = ident, stringSpaces, "=", stringSpaces, value, "\n", spaces .
ident = identChar, {identChar} .
identChar = letter | digit | "_" | "." | "," | ":" | "(" | ")" | "{" | "}" | "-" | "#" | "@" | "&" | "*" | "|" .
value = {not "\n"} .
stringSpaces = {" " | "\t"} .
spaces = {" " | "\t" | "\n" | "\r"} .

Её описание нам скоро понадобится.

C++ и Boost Spirit



Начните с установки boost (можно взять на официальном сайте или поискать готовые пакеты для вашей OS). Собирать boost не требуется, так как весь Spirit живёт в хедерах. Процесс установки для разных систем может быть различным, поэтому я не буду его здесь описывать.

Я постараюсь подробно описать процесс создания парсера на С++. При этом я не буду особенно думать о производительности, так как это не является целью данной статьи.

Начнём с подключения необходимых хедеров.
1 #include <fstream>
2 #include <functional>
3 #include <numeric>
4 #include <list>
5 #include <vector>
6 #include <string>
7
8 #include <boost/spirit.hpp>
9 #include <boost/algorithm/string.hpp>
10
11 using namespace std;
12 using namespace boost::spirit;

Кроме хедера самого Spirit я включил библиотеку строчных алгоритмов из boost-а (буду использовать функцию trim). Конструкция using namespace не всегда является хорошей практикой, но здесь для краткости я себе это позволю.

Определим типы данных: запись — это пара «ключ — значение», секция — это пара «ключ — список записей», все данные ini-файла — это список секций.
14 typedef pair<string, string> Entry;
15 typedef list<Entry > Entries;
16 typedef pair<string, Entries> Section;
17 typedef list<Section> IniData;

Кроме типов данных нам потребуются обработчики событий, которые будут вызываться в тот момент, когда парсер разберёт очередной нетерминал.
19 struct add_section
20 {
21 add_section( IniData & data ) : data_(data) {}
22
23 void operator()(char const* p, char const* q) const
24 {
25 string s(p,q);
26 boost::algorithm::trim(s);
27 data_.push_back( Section( s, Entries() ) );
28 }
29
30 IniData & data_;
31 };
32
33 struct add_key
34 {
35 add_key( IniData & data ) : data_(data) {}
36
37 void operator()(char const* p, char const* q) const
38 {
39 string s(p,q);
40 boost::algorithm::trim(s);
41 data_.back().second.push_back( Entry( s, string() ) );
42 }
43
44 IniData & data_;
45 };
46
47 struct add_value
48 {
49 add_value( IniData & data ) : data_(data) {}
50
51 void operator()(char const* p, char const* q) const
52 {
53 data_.back().second.back().second.assign(p, q);
54 }
55
56 IniData & data_;
57 };


Обработчики событий представляют собой функторы, которые принимают на вход кусок строки (через два указателя).
Функтор add_section будет вызываться в тот момент, когда парсер распознает очередную секцию. В качестве параметра add_section получит имя этой секции. Функтор add_key будет вызван в тот момент, когда парсер распознает имя нового параметра. Функтор add_value будет вызван в тот момент, когда парсер распознает значение параметра. При помощи этих функторов организуется последовательное заполнение IniData: сначала добавляется пустая секция (add_section), потом в эту секцию кладется Entry с незаполненным значением (add_key), а потом это значение заполняется (add_value).

Теперь будем переносить грамматику из нотации Бэкуса-Наура в C++. Для этого создаётся специальный класс inidata_parser.
59 struct inidata_parser : public grammar<inidata_parser>
60 {
61 inidata_parser(IniData & data) : data_(data) {}
62
63 template <typename ScannerT>
64 struct definition
65 {
66 rule<ScannerT> inidata, section, entry, ident, value, stringSpaces, spaces;
67
68 rule<ScannerT> const& start() const { return inidata; }
69
70 definition(inidata_parser const& self)
71 {
72 inidata = *section;
73
74 section = ch_p('[')
75 >> ident[add_section(self.data_)]
76 >> ch_p(']')
77 >> stringSpaces
78 >> ch_p('\n')
79 >> spaces
80 >> *(entry);
81
82 entry = ident[add_key(self.data_)]
83 >> stringSpaces
84 >> ch_p('=')
85 >> stringSpaces
86 >> value[add_value(self.data_)]
87 >> spaces;
88
89
90 ident = +(alnum_p | chset<>("-_.,:(){}#@&*|") );
91
92 value = *(~ch_p('\n'));
93
94 stringSpaces = *blank_p;
95
96 spaces = *space_p;
97 }
98
99 };
100
101 IniData & data_;
102 };

Этот класс инкапсулирует в себе всю грамматику. Разберёмся поподробнее. В строке 59 мы видим, что парсер наследуется от шаблонного класса grammar, используя crtp, — это необходимo для правильной работы Spirit-а. Парсер принимает в конструкторе ссылку на незаполненную IniData и сохраняет её (61). Внутри парсера нужно определить шаблонную структуру definition (63-64). У структуры definitionесть члены данных типа rule — это парсеры для каждого из нетерминалов нашей грамматики в форме Бэкуса-Наура (66). Необходимо определить функцию-член start, которая будет возвращать ссылку на главный нетерминал — inidata (68).

В конструкторе definition мы описываем грамматику. Грамматика переписывается на C++ почти дословно. inidata состоит из нескольких секций (72) — это выражается звёздочкой (как замыкание Клини, но звёздочка слева). Секция начинается с квадратной скобки — для этого используется встроенный парсер ch_p, который парсит один символ. Вместо запятой из нотации Бэкуса-Наура используется оператор >>. В квадратных скобках после выражения пишется функтор-обработчик события (75, 82, 86). Символ "+" слева означает «хотя бы один», а "~" означает отрицание. alnum_p — встроенный парсер для букв и цифр. chset<> соответствует любому символу из строки (важно, что минус идёт первым, иначе он воспринимается как знак интервала, вроде «a-z»). blank_p соответствует пробельному символу в строке (пробел или табуляция), space_p соответствует любому пробельному символу (в т.ч. и переводу строки и возврату каретки).

Отметим, что нетерминалы ident и identChar удалось слить в один благодаря оператору "+" — в нотации Бэкуса-Наура это было невозможно, т.к. там отсутствует подобное обозначение.

С грамматикой всё. Осталось научиться удалять комментарии и искать значение в IniData.
Для удаления комментариев нам потребуется специальный функтор.
104 struct is_comment{ bool operator()( string const& s ) const { return s[] == '\n' || s[] == ';'; } };

Теперь напишем функцию поиска в IniData.
106 struct first_is
107 {
108 first_is(std::string const& s) : s_(s) {}
109
110 template< class Pair >
111 bool operator()(Pair const& p) const { return p.first == s_; }
112
113 string const& s_;
114 };
115
116 bool find_value( IniData const& ini, string const& s, string const& p, string & res )
117 {
118 IniData::const_iterator sit = find_if(ini.begin(), ini.end(), first_is(s));
119 if (sit == ini.end())
120 return false;
121
122 Entries::const_iterator it = find_if(sit->second.begin(), sit->second.end(), first_is(p));
123 if (it == sit->second.end())
124 return false;
125
126 res = it->second;
127 return true;
128 }

Вместо функтора first_is можно применить boost::bind, но я решил не мешать всё в одну кучу. С поиском всё просто: сначала в списке ищем секцию по имени, потом в списке записей секции ищем параметр по имени, и, если всё нашлось, то возвращаем значение параметра через параметр-ссылку.

Осталось написать main.
130 int main( int argc, char** argv)
131 {
132 if ( argc != 4 )
133 {
134 cout << "Usage: " << argv[] << " <file.ini> <section> <parameter>" << endl;
135 return ;
136 }
137
138 ifstream in(argv[1]);
139 if( !in )
140 {
141 cout << "Can't open file \"" << argv[1] << '\"' << endl;
142 return 1;
143 }
144
145 vector< string > lns;
146
147 std::string s;
148 while( !in.eof() )
149 {
150 std::getline( in, s );
151 boost::algorithm::trim(s);
152 lns.push_back( s+='\n' );
153 }
154 lns.erase( remove_if(lns.begin(), lns.end(), is_comment()), lns.end());
155 string text = accumulate( lns.begin(), lns.end(), string() );
156
157 IniData data;
158 inidata_parser parser(data); // Our parser
159 BOOST_SPIRIT_DEBUG_NODE(parser);
160
161 parse_info<> info = parse(text.c_str(), parser, nothing_p);
162 if (!info.hit)
163 {
164 cout << "Parse error\n";
165 return 1;
166 }
167
168 string res;
169 if (find_value(data, argv[2], argv[3], res))
170 cout << res;
171 else
172 cout << "Can't find requested parameter";
173 cout << endl;
174 }


Строки 132-136 — проверяем параметры программы: если их не 4, то выводим usage. Если с параметрами всё ок, то открываем файл (138-143). Если и с файлом всё нормально, то создаём массив строк lns (145) и считываем в него весь файл (147-153). После этого удаляем оттуда комментарии, используя припасённый функтор is_comment (154). В заключение склеиваем все строчки в одну (155).

В строчках 157-159 создаётся и инициализируется парсер. Теперь запускаем парсер — для этого используется функция parse, которая принимает на вход сам текст, парсер и специальный парсер для пропускаемых символов (скажем, мы хотели бы пропускать все пробелы). В нашем случае парсер для пропускаемых символов будет пустым — nothing_p (т.е. ничего не парсящий). Результатом функции parse является структура parse_info<>. Нас интересует булево поле hit этой структуры, которое истинное, если не произошло ошибок. В строчках 162-166 мы сообщаем, если произошла ошибка. Осталось только найти параметр, заданный в командной строке и вывести его значение (168-173).

Теперь код полностью написан. Компилируем его и запускаем на тестовом примере.
$ g++ ini.cpp -o ini_cpp

$ ./ini_cpp /usr/lib/firefox-3.0.5/application.ini App ID
{ec8030f7-c20a-464f-9b0e-13a3a9e97384}

$ ./ini_cpp /usr/lib/firefox-3.0.5/application.ini App IDD
Can't find requested parameter


Надеюсь, что данная статья поможет вам написать свой собственный парсер =)


P.S.: Наглым образом спёрто отсюда: Создаём парсер для ini-файлов на C++

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

  • Gregorian
  • Posix Time
  • Local Time

Сейчас наверно сложно найти человека, который не столкнулся бы с необходимостью использования времени в своих целях, а точнее его цифрового представления. Цели могут быть совершенно разными, от простых замеров интервалов времени, в которые выполняется некий участок кода(совершенно не верный метод профайлинга, между прочим, но сейчас не об этом), до поддержки полноценных календарей в своих приложениях, которые должны быть актуальны для любого человека на земном шаре. Все эти манипуляции можно выполнить с помощью довольно аскетичной и простой в использовании, но в тоже время мощной библиотеки Boost.Date_Time. В библиотеке учитываются: високосный год, високосная секунда, летнее время и т.д
Разберемся с некоторой терминологией, используемой в библиотеке:
В библиотеке существую три глобальных типа разделения времени:
  1. Time Point — некая точка во временном континууме, например дата Вашего рождения
  2. Time Duration — интервал времени, не привязанный ни к какой точке временного континуума
  3. Time Interval — интервал времени, привязанный к некой точке во временном континууме

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

boost::gregorian


Все примеры, используемые в данной статье подразумевает использование using namespace boost::<соответствующий_namespace> в начале каждого компилируемого юнита.
Данный компонент представляет нам Грегорианский календарь и все мыслимые возможности работы с ним. Он в свою очередь делится на следующие компоненты:

Разберем назначение каждой из них:

boost::gregorian::date — предназначен для простого хранения даты – точки во временном континууме. Поддерживает создание даты из строки определенного формата, а также посредством класса boost::date_time::day_clock, основанного на стандартном механизме С\С++ связанным с time_t
Помимо хранения даты(которая не может быть изменена у объекта, кроме как посредством operator=) у класса есть методы* для получения конкретных частей даты(year(), month(), day() etc.), преобразования внутреннего представления даты в строку, определенного формата(to_simple_string(),to_iso_string(),to_iso_extended_string()) и конвертации в(и обратно) стандартную С\С++ структуру tm(to_tm(),date_from_tm())

*- я не привожу описание каждой функции, более того я не буду, приводит полный список имеющихся функций, их список Вы можете посмотреть в ссылках, соответствующих конкретному классу. Функций довольно много и они довольно просты в использовании, так же я опускаю наличие параметров функции, если считаю это незначительным в данный момент.
Пример:
date xGPWStart(1941, Jun, 22);
date xNowdays = day_clock::local_day();
std::cout << "The Great Patriotic War was started in " << xGPWStart << std::endl;
std::cout << "And the current data is " << xNowdays;


* This source code was highlighted with Source Code Highlighter.


Вывод:
The Great Patriotic War was started in 1941-Jun-22
And the current data is 2009-Jul-26

boost::gregorian::date_duration — служит для подсчета дней, используя boost::gregorian::date для вычислений.
Для удобства работы с date_duration существуют три класса: months_duration, years_duration и weeks_duration(так же есть typedef’ы на эти типы, представленные для удобства: months, years и weeks соответственно), которые могут быть прибавлены или вычтены из date_duration чтобы получить желаемый результат. Существует подводный камень, связанный с этими тремя классами. Если использовать их в своих вычислениях, то Вы можете получить результат, который Вы не ожидаете. Приведу пример:
date xSomeDay(1999, Jan, 28);
date xDayInNextMonth;
std::cout << "That's right: " << ( xDayInNextMonth = xSomeDay + months(1) ) << std::endl;
std::cout << "And that's not: " << xDayInNextMonth + months(1);

* This source code was highlighted with Source Code Highlighter.

That's right: 1999-Feb-28
And that's not: 1999-Mar-31

Такое поведение обусловлено особенностью класса months_duration, который будет всегда использовать конец месяца в арифметических операциях, если изначальный объект указывал на одно из возможных чисел, которыми оканчиваются месяцы(28, 29, 30, 31). Будьте внимательны при использовании данного типа, кстати month_iterator лишен этого недостатка(преимущества?), но о нем поговорим позже.

boost::gregorian::date_period — класс представлен для удобного представления интервала между двумя датами, может быть использован для определения вхождения определенной даты во временной интервал(contains()), пересечения интервалов(intersects(), intersection()), смежности дат(is_adjacent()) и определения отношения расположения одной даты относительно другой(is_after(), is_before()). Кроме того, существуют методы для комбинации интервалов(merge(), span()) и их изменения(shift(), expand()). Важно помнить, что последний дегь в периоде не входит в весь период, т.е в периоде 1-Jan-1999\10-Jan-1999 последним днем будет 9 января, а не 10.
Пример:
date_period xGPWDuration( date(1941, Jun, 22), date(1945, May, 9) );
date_period xStalinLifeYears( date(1878, Dec, 18), date(1953, Mar, 6) ); date_period xJukovsIncorrectLifeYears( date(1896, Dec, 6), date(1974, Jun, 14) );
std::cout << "The Great Patriotic War duration is " << xGPWDuration << std::endl;
std::cout << "Was the GPW inside the Stalin's life years? " << std::boolalpha << xStalinLifeYears.contains(xGPWDuration) << std::endl;
std::cout << "Jukov's incorrect life years is " << xJukovsIncorrectLifeYears << std::endl;
xJukovsIncorrectLifeYears.expand( days(5) );
std::cout << "Jukov's correct life years is " << xJukovsIncorrectLifeYears << std::endl;
//Last day isn't included in the interval
date_period xFirstPeriod( date(1999, Jan, 1), date(1999, Jan, 10) );
date_period xSecondPeriod( date(1999, Jan, 10), date(1999, Jan, 12) );
std::cout << "Does these periods intersect? " << std::boolalpha << xFirstPeriod.intersects(xSecondPeriod) << std::endl;

* This source code was highlighted with Source Code Highlighter.

Вывод:
The Great Patriotic War duration is [1941-Jun-22/1945-May-08]
Was the GPW inside the Stalin's life years? true
Jukov's incorrect life years is [1896-Dec-06/1974-Jun-13]
Jukov's correct life years is [1896-Dec-01/1974-Jun-18]
Does these periods intersect? false

boost::gregorian::date_iterator — как должно быть понятно из названия – это типичный итератор, предназначенный для “движения” по датам. date_iterator сам по себе не интересен, т.к является абстрактным классом, более интересны его классы-наследники: day_iterator, week_iterator, month_iterator, year_iterator.
В качестве примера используем пример из date_duration, в котором мы получали некорректную дату (из-за подводных камней с месяцами). Как я и упоминал раньше, в date_iterator подобных проблем нет:
month_iterator xSomeDay(date(1999, Jan, 28));
std::cout << "That's right: " << *++xSomeDay << std::endl;
std::cout << "And that's too!: " << *++xSomeDay;

* This source code was highlighted with Source Code Highlighter.

That's right: 1999-Feb-28
And that's too!: 1999-Mar-28


Алгоритмы по работе с датами — набор разнообразных классов и функций, для различных манипуляций над датами. Каждый класс имеет метод get_data() который позволяет получить дату, сгенерированную данным классом. Классы предоставляют нам следующий функционал:
  • Получить первый, последний или произвольный день для заданного месяца и недели(first_day_of_the_week_in_the_month(), last_day_of_the_week_in_the_month(), nth_day_of_the_week_in_the_month). День недели для поиска задается.
  • Задать частичную дату(без указания года)(partial_date()). А потом получать полную дату, с помощью get_data()
  • Вычислить первый день недели до или после заданной даты(first_day_of_the_week_before(), first_day_of_the_week_after()). День недели для вычисления задается

Функции предоставляют следующий функционал:
  • Вычислить кол-во дней начиная от текущей даты, до следующего или предыдущего, заданного, дня недели(days_until_weekday(), days_before_week_day()).
  • Сгенерировать дату, которая будет является датой следующего или предыдущего, заданного, дня недели. Новая дата будет сгенерирована, относительно предварительно заданной даты.

Примеры:
last_day_of_the_week_in_month xLastFriday(Friday, Jul);
partial_date xJunTen(10, Jun);
std::cout << "What is the date of the last friday in the July 2009? " << xLastFriday.get_date(2009) << std::endl;
std::cout << "Just dusplay 10 Jun of 2009 " << xJunTen.get_date(2009) << std::endl;
std::cout << "How much days from now till next friday? " << days_until_weekday( day_clock::local_day(), greg_weekday(Friday) )<< std::endl;

* This source code was highlighted with Source Code Highlighter.

Вывод:
What is the date of the last friday in the July 2009? 2009-Jul-31
Just dusplay 10 Jun of 2009 2009-Jun-10
How much days from now till next friday? 5

boost::gregorian::gregorian_calendar — предоставляет полезный набор статических функций для работы с датами.
Вместо описания функций, приведу пример их использования(функции просты и их название говорит само за себя):
std::cout << "What the day of the GPW begining? " << DayToString( gregorian_calendar::day_of_week( gregorian_calendar::ymd_type(1941, Jun, 22) ) ) << std::endl;
std::cout << "And what is the number of this day frome the epoch start? " << gregorian_calendar::day_number( gregorian_calendar::ymd_type(1941, Jun, 22) ) << std::endl;
std::cout << "And what is the number of this day frome the epoch start? " << gregorian_calendar::day_number( gregorian_calendar::ymd_type(1400, Jan, 1) ) << std::endl;
std::cout << "What is the last day in the February 1941? " << gregorian_calendar::end_of_month_day(1941, Feb) << std::endl;
std::cout << "What is the date of the 3333333 day from the epoch start? " << date( gregorian_calendar::from_day_number(3333333) ) << std::endl;
std::cout << "Is the 2004 year a leap year? " << std::boolalpha << gregorian_calendar::is_leap_year(2004) << std::endl;

* This source code was highlighted with Source Code Highlighter.

Вывод:
What the day of the GPW begining? Sunday
And what is the number of this day frome the epoch start? 2430168
And what is the number of this day frome the epoch start? 2232400
What is the last day in the February 1941? 28
What is the date of the 3333333 day from the epoch start? 4414-Apr-03
Is the 2004 year a leap year? true

Эмпирическим путем было получено, что для функций day_number() и from_day_number() минимальными значениями являются 1400-Jan-1 и 2232400 соответственно. Если пытаться использовать дату ранее 1400-Jan-1, то получим исключение. То же справедливо и для количества дней.

boost::posix_time


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

Разберем назначение каждой части:

boost::posix_time::ptime — представляет собой точку во временном континууме. Очень похожа на boost::gregorian:date но с разрешением до микросекунд. При создании экземпляра класса только по gregorian:date, “низкоразрешающая” часть устанавливается в полночь(все нули).
Пример использования:
ptime xTime(date(1961, Apr, 12), hours(9) + minutes(7));
std::cout << "Did you know that Gagrin said \"Poehali\" at " << xTime << "\n";
ptime xTimeStr( time_from_string("1961-04-12 09.07.00.0000") );
std::cout << "And the same time point constructed from a string: " << xTimeStr << "\n";
std::cout << "Current time with second resolution: " << second_clock::local_time() << "\nAnd with microsecond:" << microsec_clock::local_time();

* This source code was highlighted with Source Code Highlighter.

Вывод:
Did you know that Gagrin said «Poehali» at 1961-Apr-12 09:07:00
And the same time point constructed from a string: 1961-Apr-12 09:07:00
Current time with second resolution: 2009-Jul-29 16:41:51
And with microsecond:2009-Jul-29 16:41:51.087000


boost::posix_time::time_duration — представляет собой длительность во времени, которая не привязана к конкретной дате. Максимальное разрешение длительности ограничено наносекундами, если библиотека собрана с макросом BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG, и микросекундами, по умолчанию. Из объекта может быть получена информация о количестве секунд\микросекунд\миллисекунд\наносекунд(при соответствующей сборке) которые содержатся в текущей временной длительности.
Пример:
time_duration xTime(1,2,3);
std::cout << "Print time: " << xTime << "\n";
std::cout << "Print increased time: " << xTime + hours(3) + seconds(2) + minutes(6) + milliseconds(15) + microseconds(25) << "\n";
std::cout << "Print total seconds: " << xTime.total_seconds() << " milliseconds: " <<
xTime.total_milliseconds() << " microseconds: " << xTime.total_microseconds() << "\n";

* This source code was highlighted with Source Code Highlighter.

Вывод:
Print time: 01:02:03
Print increased time: 04:08:05.015025
Print total seconds: 3723 milliseconds: 3723000 microseconds: 3723000000

boost::posix_time::time_period — представляет собой отрезок во времени, класс схож gregorian::date_period, но имеет более низкую разрешающую способность. Функционал класса позволяет определять вхождение(contains()), пересечение(intersects()) и длину(length()) интервалов. Так же существует возможность расширения(expand()), смещения(shift()) и объединения(merge()) интервалов.
Пример:
ptime xDoomsday( date(2012, Jan, 1) );
time_period xArmageddonLast(xDoomsday, hours(1));
time_period xChakNorrisSmoke(xDoomsday, minutes(1));
std::cout << "Doomsday was during: " << xArmageddonLast<< "\n";
std::cout << "Chak Norris was smoking at " << xChakNorrisSmoke << "\n";
std::cout << "Did Chak Norris smoke during Doomsday breathtaking?" << std::boolalpha <<xArmageddonLast.contains(xChakNorrisSmoke);


* This source code was highlighted with Source Code Highlighter.

Вывод:
Doomsday was during: [2012-Jan-01 00:00:00/2012-Jan-01 00:59:59.999999]
Chak Norris was smoking at [2012-Jan-01 00:00:00/2012-Jan-01 00:00:59.999999]
Did Chak Norris smoke during Doomsday breathtaking?true

boost::posix_time::time_iterator — предназначен(как наверное все догадались :) ) для итерации по времени. Приятной особенностью данного итератора является возможность задания временного интервала, который будет использоваться при итерации, т.е. насколько и в каких единицах будет изменяться текущая точка во временном континууме при каждой итерации. В качестве временных единиц могут быть использованы все единицы от часа до наносекунд (если собрано с соответствующим флагом)
Приведу маленький пример:
ptime xTime(date(2012, Jan, 1));
time_iterator xIt(xTime, hours(6));
std::cout << "6 hours after Domsday has come!!!" << *++xIt;

* This source code was highlighted with Source Code Highlighter.

Вывод:
6 hours after Domsday has come!!!2012-Jan-01 06:00:00

Local Time System


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


boost::local_time:: posix_time_zone представляет собой набор данных и правил для представления часовых поясов(смещение относительно GMT, правила перехода на летнее время и обратно, название часового пояса и его аббревиатура). Объект данного типа создается на основании строки, формат строки является стандартизированным POSIX форматом(IEEE Std 1003.1) для часовых поясов.
В общем виде эта строка выглядит следующим образом:
std offset dst [offset],start[/time],end[/time]
std — Аббревиатура часового пояса.
offset — Смещение относительно GMT.
dst — Аббревиатура часового пояса, во время действия летнего времени.
[offset] — Показывает насколько изменяется время (в часах), при переходе на летнее время. Опциональный параметр.
start и end — Задают интервал действия летнего времени.
[/time] — Задает точное время в пределах дня, в которое начинается переход на летнее время или летнее время прекращает свое действие.
offset и time имеют следующий формат: [+|-]hh[:mm[:ss]] {h=0-23, m/s=0-59}
start и end могут быть представлены в одном из следующих форматов:
  • Mm.w.d {month=1-12, week=1-5 (5 всегда последняя), day=0-6}
  • Jn {n=1-365 29 февраля – не учитывается}
  • n {n=0-365 Учитывает 29 февраля в високосные годы}

Каждую часть этой строки вы может получить отдельно, с помощью методов данного класса. Не вижу смысла приводить здесь их имена, они достаточно прозрачны и отражают суть их назначения, так что обратитесь к документации, за их списком.
В качестве примера я использовал часовой пояс GMT+3(Московское время):
posix_time_zone xZone("MSK+3MSD+01,M3.5.0/02:00,M10.5.0/02:00");
std::cout << "Dailight period in 2009 started at " << xZone.dst_local_start_time(2009) << "\nAnd it will finish at " << xZone.dst_local_end_time(2009);


* This source code was highlighted with Source Code Highlighter.


Вывод:
Dailight period in 2009 started at 2009-Mar-29 02:00:00
And it will finish at 2009-Oct-25 02:00:00


boost::local_time::tz_database — удобный класс для хранения множества различных часовых поясов. При создании объекта, создается пустая база данных (громко сказано конечно :) ), после чего она может быть вручную заполнена, с помощью метода add_record() или же прочитана из csv(comma separated values) файла, пример такого файла(с большим количеством записей) содержится в %boost%\libs\date_time\data\date_time_zonespec.csv
Формат записи внутри этого файла, должен удовлетворять следующему стандарту:
«ID»,«STD ABBR»,«STD NAME»,«DST ABBR»,«DST NAME»,«GMT offset»,«DST adjustment»,«DST Start Date rule»,«Start time»,«DST End date rule»,«End time»

Где:
ID – Содержит строку, однозначно идентифицирующую данный часовой пояс.
STD ABBR, STD NAME, DST ABBR, DST NAME — Эти поля заполняются строками с именами и аббревиатурами стандартного и летнего времен, зачастую имена и аббревиатуры идентичны.
GMT offset — Смещение во времени, относительно Гринвича. Его формат: {+|-}hh:mm[:ss]
DST adjustment — Смещение относительно GMT offset во время действия “летнего времени”. Формат тот же, что и у GMT offset.
DST Start Date rule — Строка описывающая день в году, который возвещает начало периода “летнего времени”. Так же имеет свой формат(сколько можно форматов разных?):
weekday;day-of-week;month
Где:
weekday — Порядковое числительное, индицирующее какой по счету день в месяце, нас интересует.
day-of-week — день недели.
month — месяц.
Пример:
-1;0;3 – Начало “летнего времени” в России(последнее воскресенье марта)

Start time — Время после полуночи, в которое вступает в силу “летнее время”. Формат тот же, что и у GMT offset.
DST End date rule — Строка описывающая день в году, который возвещает конец периода “летнего времени”. Формат идентичен DST Start Date rule.
End time — Аналог Start time, только для окончания “летнего времени”.
Пример:
tz_database xDb;
xDb.load_from_file("G:\\Program files\\boost\\boost_1_39_0\\\libs\\date_time\\data\\date_time_zonespec.csv");
const std::vector<std::string>& xAllRegions = xDb.region_list();
std::cout << "Print first 10 zone IDs from the boost time zone file:" << std::endl;
for(std::vector<std::string>::const_iterator it = xAllRegions.begin(); it != xAllRegions.begin() + 10; ++it)
std::cout << *it << std::endl;
std::cout << "And time when daylight saving was started at 2009: " << xDb.time_zone_from_region("Europe/Moscow")->dst_local_start_time(2009) << std::endl;

* This source code was highlighted with Source Code Highlighter.


Вывод:
Print first 10 zone IDs from the boost time zone file:
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmera
Africa/Bamako
Africa/Bangui
Africa/Banjul
Africa/Bissau
Africa/Blantyre
And time when daylight saving was started at 2009: 2009-Mar-29 02:00:00


boost::local_time::custom_time_zone — класс для создания описания часового пояса, но в отличие от прежде описанного boost::local_time::posix_time_zone , данный класс использует четыре других класса, при конструировании часового пояса. Это классы: time_duration , time_zone_names , dst_adjustment_offsets и dst_calc_rule. Так как класс не выделяется ничем выдающимся, то просто приведу пример его использования:
time_zone_names xNames("Moscow Standart Time", "MST", "Moscow Daylight Time", "MDT");
time_duration xGMTOffset(3, 0, 0);
dst_adjustment_offsets xRulesOffsets( time_duration(1,0,0), time_duration(2,0,0), time_duration(3,0,0) );
//Mak daylight's rule
last_day_of_the_week_in_month xStartRule(Sunday, Mar);
last_day_of_the_week_in_month xEndRule(Sunday, Oct);

boost::shared_ptr<dst_calc_rule> xRules( new last_last_dst_rule(xStartRule, xEndRule) );
custom_time_zone xCustomTimeZone(xNames, xGMTOffset, xRulesOffsets, xRules);

std::cout << "The our time zone name is: " << xCustomTimeZone.std_zone_name() << "\n"
<< "It has an "<< xCustomTimeZone.base_utc_offset() << " offset from GMT.\n"
<< "And daylight period will end at "<< xCustomTimeZone.dst_local_end_time(2009) <<std::endl;
std::cout << "Posix string which represents our custom_time_zone object is:\n" << xCustomTimeZone.to_posix_string();

* This source code was highlighted with Source Code Highlighter.

Вывод:
The our time zone name is: Moscow Standart Time
It has an 03:00:00 offset from GMT.
And daylight period will end at 2009-Oct-25 03:00:00
Posix string which represents our custom_time_zone object is:
MST+03MDT+01,M3.5.0/02:00,M10.5.0/03:00

Классы boost::local_time::local_date_time и boost::local_time::local_time_period повторяют схожие классы из boost::posix_time, с привязкой к часовому поясу, поэтому я не буду их рассматривать.

Полезные особенности и заключение


В Boost.Data_time, помимо классов для создания дат, есть полезные утилиты, такие как: форматируемая обработка ввода\вывода и сериализация. Оба механизма достаточно просты.


P.S.: Наглым образом спёрто отсюда: Boost это просто. Часть 2. Boost.Date_time.

Использование интеллектуальных указателей из boost

Ни для кого не секрет, что работа с динамической памятью и системными ресурсами вообще является одним из самых сложных аспектов при программировании на С++. Сложность этой проблемы заключается в том, что память и ресурсы имеют обыкновение утекать… Безвозвратно и бесконтрольно. И если синтаксические и логические ошибки выловить в программе достаточно просто, то на поиск утечек памяти уходит не одна неделя. За все время существования этой проблемы разработчиками было придумано множество путей облегчения себе жизни. Кто-то, избрав путь наименьшего сопротивления, перешел на такие языки, как Java или C#, где за ресурсами следить вроде как не надо – на это существуют сборщики мусора, а динамическая память (в понимании С++) отсутствует как класс. Кто-то вооружившись BoundsChecker’ом анализирует результаты работы программы с целью выявить места, в которых происходит утечка. Кто-то, набравшись терпения, пишет собственные менеджеры памяти и сборщики мусора для С++. А кто-то, взяв на вооружения некоторые идеи языка С++, разрабатывает т. н. ведущие указатели, дабы, оставаясь в рамках языка, исключить саму возможность утечек.

Что есть ведущий указатель (или, иначе говоря Smart Pointer)? Это объект, поведение которого не отличается (ну, или почти не отличается) от поведения обычного указателя, но обладающий при этом рядом достоинств, отсутствующих у обычного указателя. Вообще говоря, по этому поводу написано столько, что писать еще раз тоже самое – большого смыла нет. Достаточно лишь упомянуть вот о чем. Ведущие (или умные) указатели обычно подразделяются на два основных типа – «слабые» и с «подсчетом ссылок». Слабые указатели позволяют без лишних затрат освободить хранящийся в них объект по выходу из блока, в котором такой указатель был объявлен. Тем самым решается проблема «потерянных ссылок», когда разработчик, по невнимательности, забывает освобождать динамическую память, указатель на которую сохраняется на стеке:

void SomeFunc()
{
char* buff = new char[16];
_itoa(125, buff, 10);

}

В этом примере буфер был выделен, но не был удален. Указатели же с подсчетом ссылок позволяют контролировать объект в течение гораздо более длительного срока. Достигается это тем, что такой указатель «считает ссылки» на указываемый объект и удаляет только тогда, когда контролируемый объект уже никому не нужен. Пример: Объявляется класс:

class SomeClass
{
public:
   SomeClass() {m_SomeObject = new int;}

   ~SomeClass() {delete m_SomeObject;}
private:
   int *m_SomeObject;
};
// потом где-то в коде имеет место такая конструкция:

SomeClass obj1;
{
   SomeClass obj2(obj1);
   // Что-то делаем с объектом obj1
}
// Что-то делаем с объектом obj2

Что произойдет? Правильно. После выхода из блока и вызова деструктора для obj2 указатель в obj1 станет недействительным. Избежать этого можно переписав конструктор копий и оператор присваивания. Но есть и более простой путь – воспользоваться указателем с подсчетом ссылок. Такой указатель можно реализовать самому. И наверняка многие так и делают. Но это не что иное, как изобретение велосипеда, название которому – boost::smart_ptr.

Как это использовать

scoped_ptr, shared_ptr, scoped_array, shared_array

Этот раздел библиотеки включает в себя группу классов, реализующих обе вышеописанные разновидности указателей: boost::scoped_ptr и boost::shared_ptr Первый используется для контроля времени жизни указателя в пределах операторного блока. Второй – для длительного контроля за указателем. Существуют разновидности этих классов для указателей на массивы: boost::scoped_array и boost::shared_array соответственно. Разница между этими группами классов очевидна. Первые для удаления контролируемого указателя используют оператор delete, а вторые – оператор delete[]. По этому путать их не рекомендуется во избежании возможных неприятностей с менеджером памяти. В общем случае использовать экземпляры этих классов можно почти также, как и указатели:

boost::shared_ptr<SomeClass> ptr(new SomeClass);
ptr->SomeFunc(); // вызываем метод класса по указателю

std::cout << *ptr; // выводим экземпляр класса в поток.
// а вот и то самое «почти»:
SomeClass* ptr1;
if (ptr == ptr1) // тут будет ошибка компиляции

   // что-то делаем

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

Метод Назначение
Общие
reset Уменьшает счетчик ссылок на единицу (если для shared_ptr) и, если необходимо, удаляет объект
swap Обменивает значения двух объектов
scoped_array/shared_array
operator [] Предоставляет доступ по индексу
shared_ptr/shared_array
unique Возвращает true, если счетчик ссылок == 1
use_count Возвращает значения счетчика ссылок
operator bool Возвращает true, если указатель проинициализирован

Подробно о сигнатурах этих методов можно прочитать в документации. Приведенные выше примеры кода с использованием этих классов будут выглядеть так:

void SomeFunc()

{
scoped_array<char> buff = new char[16];
_itoa(125, buff.get(), 10);

}

class SomeClass
{
public:
   SomeClass() {m_SomeObject.reset(new int);}

private:
   boost::shared_ptr<int> m_SomeObject;
};

Применение этих классов достаточно прозрачно, и не должно представлять каких-либо затруднений. Кроме этих четырех классов в джентельменский набор умных указателей входят еще два класса-помошника: boost::weak_ptr и boost::intrusive_ptr

weak_ptr

Первый (boost::weak_ptr) используется в паре с shared_ptr для хранения ссылок на объекты типа boost::shared_ptr. Так же, как и обычные ссылки/указатели на shared_ptr, weak_ptr не увеличивает счетчик ссылок. Но, в отличии от обычных ссылок/указателей, weak_ptr предоставляет дополнительный сервис, заключающийся в том, что если хранимый в нем объект в какой-то момент времени удаляется «извне», то попытка получения его из weak_ptr приводит к исключению. Используется weak_ptr в случае, когда постоянное владение объектом не нужно, но, тем не менее, необходим прозрачный контроль над ним. Например:

std::vector<boost::shared_ptr<SomeClass> > m_Vector;
std::map<std::string, boost::weak_ptr<SomeClass> > m_Map;

Предполагается, что m_Vector хранит и владеет экземплярами класса SomeClass, а m_Map – предоставляет быстрый доступ к этим объектам по некоторой строке. Инициализироваться они будут так:

m_Vector.push_back(boost::shared_ptr<SomeClass>(new SomeClass));
m_Map.insert(std::make_pair(m_Vector.back()->Key, m_Vector.back());

Теперь, если мы удалим какой-либо элемент из вектора, при попытке доступа к нему по ключу приведет к исключению. При этом дополнительных проверок на действительность ссылок делать не надо.

intrusive_ptr

Второй класс (boost::intrusive_ptr) используется при работе с объектами, использующими встроенный счетчик ссылок. Пример – COM-объекты. Этот класс предполагает, что определены функции intrusive_ptr_add_ref и intrusive_ptr_release (не являющиеся фунциями-членами хранимого класса), фактически увеличивающие или уменьшающие счетчик ссылок объекта. Для COM-объектов использование такого указателя могло бы выглядеть так:

int intrusive_ptr_add_ref(IUnknown* p) {return p->AddRef();}
int intrusive_ptr_release(IUnknown* p) {return p->Release();}

после чего можно смело писать: boost::intrusive_ptr<ISomeInterface> ptr(…);

Этот класс имеет интерфейс, аналогичный классу boost::shared_ptr, за исключением того, что его нельзя использовать в связке с boost::weak_ptr. Функциональность описанных классов покрывает большинство задач, которые могут возникнуть при решении проблем с контролем указателей. Если же потребуется что-то специфическое (что мало вероятно) всегда можно изобрести свой собственный велосипед и использовать его. Приведенное описание не претендует на полноту. Были опущены некоторые моменты, связанные с использованием классов, в частности класса weak_ptr. Для получения более полной информации можно заглянуть непосредственно в документацию: http://www.boost.org/libs/smart_ptr/smart_ptr.htm

P.S.: Наглым образом спёрто отсюда: [[doc:cpp:boost:shared_ptr]]

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

Одним из самых мощных средств стандартной библиотеки языка являются алгоритмы. Их в STL много. А там, где алгоритмы – появляются функторы и их разновидности – предикаты. И все было бы замечательно, если бы не одна большая ложка дегтя – средства стандартной библиотеки предоставляют слишком мало возможностей для описания функуторов. Все просто, если в алгоритм необходимо передать какую-либо глобальную функцию, или простой указатель на метод класса, хранящегося в контейнере.

Но когда речь заходит о чем-то более сложном, как-то:

  • передача заданного значения в предикат;
  • передача указателя на метод класса, не являющегося членом контейнера;
  • предварительная обработка элементов контейнера перед передачей их в функтор;
  • передача в функтор значений элементов данных объектов обрабатываемой последовательности;
  • (список можно продолжить…)

то STL уже не может предложить чего-либо более-менее пригодного к использованию. Сразу же оказывается, что std::binder1st и std::binder2nd накладывают существенные ограничения на принимаемые параметры (кто не сталкивался с «reverence to reference is not allowed» при использовании их в связке с mem_fun_ref?), в стандартные предикаты нельзя передать результат выполнения некоторой операции над элементом контейнера, std::mem_fun и std::mem_fun_ref позволяют работать только с объектами, являющимися членами обрабатываемой последовательности… Вообщем мрак. Проблемы некоторым образом решаются с помощью написания собственных функторов и адаптеров функторов. Но разработка минимально необходимого джентльменского набора – сама по себе нетривиальная задача. Тем более, что она уже имеет весьма элегантное решение, называемое boost::bind. Без преувеличения можно сказать, что по сравнению со связкой boost::bind/boost::mem_fn близкие по функциональности классы из STL кажутся жалкой поделкой. И не без основания. При использовании классов из STL приходится всякий раз задумываться – а какой из вариантов связывателя подойдет к данному конкретному случаю, и подойдет ли вообще. С boost::bind о подобных вещах заморачиваться не надо. К каждому варианту использования компилятор самостоятельно подберет необходимый связыватель. Или выдаст ошибку, если соответствующего варианта подобрать нельзя. В коде использовать этот класс не просто, а очень просто. Рассмотрим несколько типовых вариантов.

Использование с глобальной функцией

Предположим, что есть функция:

bool compare(int a, int b) {return a < b;}

для того, чтобы использовать эту функцию в, например, алгоритме sort нам необходимо написать:

std::vector<int> vec(…);
std::sort(vec.begin(), vec.end(), compare);

Теперь немного усложним задачу – в зависимости от флага нам необходимо сортировать либо по возрастанию, либо по убыванию. При использовании только STL пришлось бы писать две функции. Накладно. С помощью boost эту задачу решить значительно проще:

bool compare(int a, int b, bool reverseSort) {return reverseSort ? a > b : a < b;}

 
std::vector<int> vec(…);
std::sort(vec.begin(), vec.end(), boost::bind(compare, _1, _2, order));

где order – булевская переменная, задающая порядок сортировки. Последняя строчка выглядит несколько странновато, но именно в ней и заключается вся суть. В результате ее компиляции будет получен код, реализующий сортировку массива, использующий для сравнения функцию compare, и передающей ей на вход три аргумента – два числа, которые необходимо сравнить, и способ их сравнения. Почему именно так? Рассмотрим этот код подробнее, записав его так:

int a = 1, b = 2;
boost::bind(compare, _1, _2, false)(a, b);

(что-то подобное содержится в недрах функции std::sort). Последняя строчка в этом коде обозначает следующее:

boost::bind – функция, создающая необходимый нам связыватель. Возвращает экземпляр функционального объекта (функтора), для которого применим оператор вызова функции. (compare, - первый аргумент функции определяет имя/указатель на функцию, которая должна быть вызвана в связывателе. В данном случае это функция compare; _1, _2 – плейсхолдеры, определяющие логику передачи параметров из оператора вызова функции в функцию compare. , false) – значение последнего аргумента, передаваемого в compare. (a, b) – оператор вызова функции, производящий связывание аргументов и вызов функции compare. В итоге будет выполнена следующий код:

compare(a, b, false);

Из приведенного примера можно увидеть, что упомянутые выше плейсхолдеры определяют связь между аргументами у оператора вызова функции связывателя и аргументами вызываемой функцией. Причем, количество аргументов у оператора вызова функции связывателя определяется количеством плейсхолдеров:

boost::bind(compare, _1, 4, false)(a, b); // неправильно. 

boost::bind(compare, _1, 4, false)(a); // правильно
boost::bind(compare, _1, _2, false)(a); // неправильно

boost::bind(compare, _1, _2, false)(a, b); // правильно

Очевидно, что попытка задать в вызове boost::bind количество параметров, несоответствующее количеству обязательных параметров в вызываемой функции приведет к ошибке. Равно как и попытка указания количества параметров большего, чем количество параметров в вызываемой функции. Действуя по аналогии мы можем записать:

std::find_if(vec.begin(), vec.end(), boost::bind(compare, _1, 4, false)); // поиск первого числа, меньшего 4

std::remove_if(vec.begin(), vec.end(), boost::bind(compare, _1, 4, true)); // удаление из последовательности всех элементов, больших 4

// и т. д.

В случае использования чистого STL для каждого случая пришлось бы искать подходящий функтор… Или писать его. Здесь необходимо обратить внимание на то, что в качестве первого параметра функции bind может выступать не только указатель на функцию, но и другой функциональный объект. Например:

std::find_if(vec.begin(), vec.end(), boost::bind(std::equal_to<int>(), _1, 4));

этот строчка найдет в массиве первый элемент, равный 4.

Использование с указателями на функции члены

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

class GraphicsObject

{
public:
   void Draw(Canvas* canvas, DrawModes mode);
};
// Объявляем коллекцию объектов этого класса:
std::list<GraphicsObject*> GraphObjects;

// А теперь нам нужно отрисовать все объекты коллекции на заданном канвасе
std::for_each(GraphObjects.begin(), GraphObjects.end(); ……..);

при использовании чистого STL нам бы пришлось писать свой собственный функтор, вызывающий у переданного объекта метод Draw с заданными параметрами. Но можно воспользоваться boost::bind:

std::for_each(GraphObjects.begin(), GraphObjects.end(), 
    boost::bind(&GraphObject::Draw, _1, canvas, mode));

в результате чего мы получаем требуемый результат. У всех объектов коллекции вызывается метод Draw, которому передаются параметры canvas и mode. При использовании указателя на функцию-член класса необходимо помнить о том, что первым параметром ей должен передаваться указатель на объект, для которого эта функция должна вызываться. В приведенном примере мы указываем, что метод должен вызываться у переданного в bind единственного параметра. Но никто не мешает нам в качестве первого передаваемого параметра указать this, или любой другой указатель. Но, как говориться в современных набивших оскомину рекламах, это еще не все.

Использование с указателем на член данных

Помимо указателей на члены-функции можно использовать указатели на члены данных. Синтаксис подобный и правила использования при этом почти такие же. Возьмем задачу. Есть структура и вектор ее экземпляров:

struct Point
{

    int x;
    int y;
};
 
std::vector<Point> PointsArray;

Теперь надо удалить из этого массива все элементы, у которых координата x равна нулю. Сделать это просто:

std::remove_if(PointsArray.begin(), PointsArray.end(), 
    boost::bind(std::equal_to<int>(), boost::bind(&Point::x, _1), 0));

Т. е. для каждого элемента массива вызывается вложенный связыватель, который возвращает значение соответствующего элемента структуры, после чего производится его сравнение с нулем. Тут мы видим еще одну возможность связывателей:

Каскадное использование связывателей

Связыватели могут вкладываться друг в друга. Один из вариантов такого вкладывания проиллюстрирован предыдущем примером. Единственное, что в этом случае надо «держать в голове» - это то, что плейсхолдеры, вне зависимости от того, в каком bind'ере (по уровню вложенности) они находятся, «адресуют» параметры самого внешго binder'а. Усложним предыдущий пример. Нужно выбросить все точки, имеющие нулевые координаты:

std::remove_if( PointsArray.begin(), PointsArray.end(), boost::bind(

    std::logical_and(), 
        boost::bind(std::equal_to<int>(), boost::bind(&Point::x, _1), 0),
        boost::bind(std::equal_to<int>(), boost::bind(&Point::y, _1), 0)

    )
));

Это хотя и работает так, как хочется, но выглядит слишком наворочено. По этому начиная с версии 1.33 в boost::bind появилась новая возможность -

Перегруженные операторы

Для упрощения приведенных выше многоэтажных конструкций для boost::bind (начиная с версии 1.33 boost'а) перегружены следующие операторы: !, ==, !=, <, ?, > и >=. Таким образом, приведенное выше выражение упрощается до:

std::remove_if( PointsArray.begin(), PointsArray.end(), boost::bind(

    boost::bind(&Point::x, _1) == 0 && boost::bind(&Point::y, _1) == 0)

    ));

Использование ссылок

Одна из неприятностей заключается в том, что если связываемая функция принимает какой-то из своих аргументов по ссылке, то с использованием boost::bind его (напрямую) передать нельзя. Т. е. например, в следующем случае:

void foo(int& a, int& b);

 
int a = 1, b = 2;
boost::bind(foo, a, b);

аргументы a и b по ссылке переданы не будут. Для того, чтобы действительно передать ссылки, необходимо делать такой вызов:

boost::bind(foo, boost::ref(a), boost::ref(b));

В этом случае foo, вызываемый из связывателя, будет действительно работать со ссылками на соответствующие переменные.

Полную документацию и примеры использования можно найти в оригинальной документации: http://www.boost.org/libs/bind/bind.html

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

#include <vector>
#include <boost/bind.hpp>
#include <boost/function.hpp>

class Test
{
public:
    void f_1()
    {
        std::cout << "void f()" << std::endl;
    }

    void f_2(int i)
    {
        std::cout << "void f_2(): " << i << std::endl;
    }

    void f_3(const int &i)
    {
        std::cout << "void f_2(): " << i << std::endl;
    }

    void two_params(int a, int b)
    {
        std::cout << "void two_params(int a, int b): " << a << ", " << b << std::endl;
    }
};

class Test2
{
public:
    void do_stuff(const std::vector<int>& v) 
    {
        std::copy(v.begin(), v.end(),  std::ostream_iterator<int>(std::cout, " "));
    }
};

void test(const std::string &a)
{
    std::cout << "void test(const std::string &a). a = " << a << std::endl;
}

void prn ( boost::function<void(int)> fn, int a )
{
    fn ( a );
}

int _tmain(int argc, _TCHAR* argv[])
{
    ////////////////////// bind //////////////////////
    std::cout << "boost::bind test" << std::endl;
    Test a;
    boost::bind(&Test::f_1, &a)();

    boost::bind(&Test::f_2, &a, 1)();
    int test_int(2);
    boost::bind(&Test::f_2, &a, _1)(test_int);

    boost::bind(&Test::f_3, &a, 3)();
    int test_int_2(4);
    boost::bind(&Test::f_3, &a, _1)(test_int_2);

    int one(100), two(200);
    boost::bind(&Test::two_params, &a, _1, _2)(one, two);
    boost::bind(&Test::two_params, &a, _2, _1)(one, two);

    std::string test_str("Hi there.");
    boost::bind(&test, test_str)();
    boost::bind(&test, _1)(test_str);
    std::cout << std::endl;
    
    ////////////////////// function //////////////////////
    std::cout << "boost::function test" << std::endl;
    
    boost::function<void (void)> func;
    func = boost::bind(&Test::f_1, &a);
    func();
    func = boost::bind(&Test::f_2, &a, 201);
    func();
    func = boost::bind(&Test::f_3, &a, 202);
    func();
    func = boost::bind(&Test::two_params, &a, 203, 204);
    func();

    boost::function<void (int)> func_2;
    func_2 = boost::bind(&Test::f_2, &a, _1);
    prn(func_2, 301);
    func_2 = boost::bind(&Test::f_3, &a, _1);
    prn(func_2, 302);

    int i_303(303), i_304(304), i_305(305), i_306(306);
    func_2 = boost::bind(&Test::two_params, &a, i_303, _1);
    prn(func_2, 1);
    func_2 = boost::bind(&Test::two_params, &a, _1, i_304);
    prn(func_2, 1);

    Test2 t;
    std::vector<int> vec;
    vec.resize(20);
    std::generate_n(vec.begin(), 20, rand);
    std::copy(vec.begin(), vec.end(),  std::ostream_iterator<int>(std::cout, " "));
    //simple_bind(&Test::do_stuff, t, _1)(vec);
    //boost::bind(&Test2::do_stuff, t, _1)(vec);

    return 0;
}

P.S.: Наглым образом спёрто здесь: [[doc:cpp:boost:bind]]