1 апр. 2009 г.

Использование идиомы boost::base_from_member

Шаблон класса boost::base_from_member предоставляет возможность создавать классы, которые выполняют инициализацию своего базового класса членом класса. Шаблон класса находится в boost/utility/base_from_member.hpp, который включается в boost/utility.hpp. Предварительное (forward) объявление шаблона класса находится в boost/utility_fwd.hpp.

Пример использования - в файле base_from_member_test.cpp.

Содержание

Обоснование

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

#include <streambuf>  // for std::streambuf
#include <ostream>    // for std::ostream

class fdoutbuf
    : public std::streambuf
{
public:
    explicit fdoutbuf( int fd );
    //...
};

class fdostream
    : public std::ostream
{
protected:
    fdoutbuf buf;
public:
    explicit fdostream( int fd )
        : buf( fd ), std::ostream( &buf )
        {}  {}
    //.};

Поведение кода неопределено, так как порядок инициализации в C++ обязывает компилятор инициализировать базовый класс прежде, чем инициализировать члены класса. R. Samuel Klatchko разработал обходной маневр с надлежащим использованием порядка инициализации. Базовые классы инициализируются в порядке объявления, так что перемещение необходимого члена в другой базовый класс, который объявлен базовым перед другим базовым классом, позволяет гарантировать надлежащую инициализацию.

Собственный базовый класс может быть сделан из идиомы:

#include <streambuf>  // for std::streambuf

#include <ostream>    // for std::ostream

class fdoutbuf
    : public std::streambuf
{
public:
    explicit fdoutbuf( int fd );
    //.../...
};

struct fdostream_pbase
{
    fdoutbuf sbuffer;

    explicit fdostream_pbase( int fd )
        : sbuffer( fd )
        {}
};

class fdostream
    : private fdostream_pbase
    , public std::ostream
{
    typedef fdostream_pbase  pbase_type;
    typedef std::ostream     base_type;

public:
    explicit fdostream( int fd )
        : pbase_type( fd ), base_type( &sbuffer )
        {}
    //...
};

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

Manually coding a base class may be better if the construction and/or copying needs are too complex for the supplied template class, or if the compiler is not advanced enough to use it.

Since base classes are unnamed, a class cannot have multiple (direct) base classes of the same type. The supplied template class has an extra template parameter, an integer, that exists solely to provide type differentiation. This parameter has a default value so a single use of a particular member type does not need to concern itself with the integer.

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

template < typename MemberType, int UniqueID = 0 >
class boost::base_from_member
{
protected:
    MemberType  member;

    base_from_member();

    template< typename T1 >
    explicit  base_from_member( T1 x1 );

    template< typename T1, typename T2 >

    base_from_member( T1 x1, T2 x2 );

    //...

    template< typename T1, typename T2, typename T3, typename T4,
     typename T5, typename T6, typename T7, typename T8, typename T9,
     typename T10 >
    base_from_member( T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7,
     T8 x8, T9 x9, T10 x10 );
};

Первый аргумент шаблонного класса MemberType - это тип члена в базовом классе (based-member). Последний аргумент шаблона UniqueID, имеющий тип int, предназначен для введения различий между несколькими базовыми классами, которые используют один и тот же  член в базовом классе (based-member type). Последний параметр шаблона имеет значение по умолчанию 0. Шаблонный класс имеет защищенное поле member, которое производный класс может использовать для последующих базовых классов (или для себя самого).

Имеется конструктор по умолчанию и несколько шаблонов конструкторов. Эти шаблоны конструктора получают до 10 (в текущей версии) аргументов и передавать их в конструктор поля класса (data member). Так как C++ в любом случае не позволяет явно указывать параметры шаблона конструктора, следует убедиться, что аргументы максимально похожи по типу на действительные типы, используемые в соответствующем конструкторе поля класса.

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

В качестве начального примера можно рассмотреть под-объект (sub-object) fdoutbuf, который нужно заключить внутри базового класса, от которого происходит наследование перед std::ostream.

#include <boost/utility/base_from_member.hpp>

#include <streambuf>  // for std::streambuf
#include <ostream>    // for std::ostream

class fdoutbuf
    : public std::streambuf
{
public:
    explicit fdoutbuf( int fd );
    //...
};

class fdostream
    : private boost::base_from_member<fdoutbuf>
    , public std::ostream
{
    // Helper typedef's
    typedef boost::base_from_member<fdoutbuf>  pbase_type;
    typedef std::ostream                        base_type;

public:
    explicit fdostream( int fd )
        : pbase_type( fd ), base_type( &member )
        {}
    //...
}; 

Идиома base-from-member является деталью реализации, которая не должна быть видима пользователям класса класса fdostream. Из-за порядка инициализации, под-объект fdoutbuf инициализируется перед под-объектом std::ostream, что позволяет безопасно использовать последний под-объект в дальнейшем конструировании под-объектов. так как под-объект fdoutbuf конечного типа является единственным под-объектом с именем "member", это имя может быть пропущено (unqualified) внутри конечного класса.

 

Пример

Шаблоны классов, использующих идиому base-from-member, типично используют единственный под-объект base-from-member, обычно для прикрепления буфера к потоку ввода вывода (a stream-buffer to an I/O stream). Следующий пример демонстрирует, как использовать множественные под-объекты base-from-member, и показывает некоторые другие моменты, связанные с именованием полей.

#include <boost/utility/base_from_member.hpp>

#include <cstddef>  // for NULL

struct an_int
{
    int  y;

    an_int( float yf );
};

class switcher
{
public:
    switcher();
    switcher( double, int * );
    //...
};

class flow_regulator
{
public:
    flow_regulator( switcher &, switcher & );
    //...
};

template < unsigned Size >

class fan
{
public:
    explicit fan( switcher );
    //...
};

class system
    : private boost::base_from_member<an_int>
    , private boost::base_from_member<switcher>
    , private boost::base_from_member<switcher, 1>
    , private boost::base_from_member<switcher, 2>
    , protected flow_regulator
    , public fan<6>

{
    // Helper typedef's
    typedef boost::base_from_member<an_int>       pbase0_type;
    typedef boost::base_from_member<switcher>     pbase1_type;
    typedef boost::base_from_member<switcher, 1>  pbase2_type;
    typedef boost::base_from_member<switcher, 2>  pbase3_type;

    typedef flow_regulator  base1_type;
    typedef fan<6>          base2_type;

public:
    system( double x );
    //...
};

system::system( double x )
    : pbase0_type( 0.2 )
    , pbase1_type()
    , pbase2_type( -16, &this->pbase0_type::member )
    , pbase3_type( x, static_cast<int *>(NULL) )
    , base1_type( pbase3_type::member, pbase1_type::member )
    , base2_type( pbase2_type::member )
{
    //...
}

Конечный класс имеет несколько подобъектов с именем "member," поэтому любое использование этого имени должно сопровождаться квалификацией именем соответствующего базового класса. (Использование оператора typedef облегчает задачу). Однако, данное решение создает новую проблему, когда необходим указатель. Использование оператора взятия адреса с под-объектом, квалифицированным именем своего класса, дает указатель-на-член (pointer-to-member) (в данном случае, к типу an_int boost::base_from_member<an_int, 0> :: *) вместо указателя на член (к типу an_int *). Новая проблема решается через квалифицирование под-объекта с "this->," и необходимо только для указателей, а не для ссылок или значений.

При инициализации происходит несколько преобразований аргументов. Аргумент конструктора pbase0_type преобразуется из double в float. Первый аргумент конструктора для pbase2_type преобразуется из int в double. Второй аргумент конструктора для pbase3_type - это особый случай необходимого преобразования; все формы литерала для нулевого указателя в C++ выглядят как целочисленная константа времени компиляции, так что C++ всегда интерпретирует такой код как целое число в случаях, когда при перегрузке функций может быть целое число или указатель. Последнее преобразование необходимо для компилятора при вызове конструктора с точно определенным типом указателя в качестве аргумента при использовании в switcher's constructor.

P.S.: Нагло спёрто отсюда: BOOST C++: идиома Base-from-Member

Комментариев нет:

Отправить комментарий