Copyright © 2001, 2003, 2004, 2012 Daryle
Walker
При разработке класса иногда базовый класс должен быть инициализирован с членом текущего класса. В качестве примера можно привести naï:
#include <streambuf>
#include <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++ требует, чтобы базовый класс был инициализирован перед членом, который он использует. Р. Самуэль Клатчко разработал способ обойти это, используя порядок инициализации в свою пользу. Базовые классы интиализируются в порядке декларирования, поэтому перемещение желаемого члена в другой базовый класс, инициализируемый перед желаемым базовым классом, может обеспечить правильную инициализацию.
Настраиваемый базовый класс может быть создан для этой идиомы:
#include <streambuf>
#include <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 ) {}
};
Другие проекты могут использовать аналогичные пользовательские базовые классы. Техника достаточно проста, чтобы создать шаблон с классом шаблонов в этой библиотеке. Основным параметром шаблона является тип прилагаемого элемента. Класс шаблонов имеет несколько (явных) шаблонов членов конструктора, которые неявно вводят аргументы конструктора и передают их члену. Класс шаблонов использует неявную конструкцию и назначение копий, отменяя их, если прилагаемый элемент не является копируемым.
Ручное кодирование базового класса может быть лучше, если требования к построению и/или копированию слишком сложны для поставляемого класса шаблонов или если компилятор недостаточно продвинут для его использования.
Поскольку базовые классы не называются, класс не может иметь несколько (прямых) базовых классов одного типа. Поставляемый класс шаблонов имеет дополнительный параметр шаблона, целое число, которое существует исключительно для обеспечения дифференциации типов. Этот параметр имеет значение по умолчанию, поэтому однократное использование определенного типа элемента не должно касаться целого числа.
#include <type_traits>
#ifndef BOOST_BASE_FROM_MEMBER_MAX_ARITY
#define BOOST_BASE_FROM_MEMBER_MAX_ARITY 10
#endif
template < typename MemberType, int UniqueID = 0 >
class boost::base_from_member
{
protected:
MemberType member;
#if C++11 is in use
template< typename ...T >
explicit constexpr base_from_member( T&& ...x )
noexcept( std::is_nothrow_constructible<MemberType, T...>::value );
#else
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 );
#endif
};
template < typename MemberType, int UniqueID >
class base_from_member<MemberType&, UniqueID>
{
protected:
MemberType& member;
explicit constexpr base_from_member( MemberType& x )
noexcept;
};
Шаблон класса имеет первый параметр шаблона MemberType
, представляющий тип базового элемента. Он имеет последний параметр шаблона UniqueID
, то есть int
, чтобы различать несколько базовых классов, которые используют один и тот же тип на основе. Последний параметр шаблона имеет значение нуля по умолчанию, если он опущен. Шаблон класса имеет защищенный элемент данных, называемый member
, который полученный класс может использовать для более поздних базовых классов (или для себя).
Если присутствуют соответствующие функции C++11, будет один шаблон конструктора. Он реализует идеальную переадресацию на лучший вызов конструктора member
(если таковой имеется). Шаблон конструктора отмечен как constexpr
, так и explicit
. Первое будет проигнорировано, если соответствующий внутренний вызов конструктора (члена
) не имеет маркера. Последний связывается другим путем, всегда вступая в силу, даже когда внутренний вызов конструктора не имеет маркера. Шаблон конструктора распространяет статус , за исключением
вызова внутреннего конструктора. (Храм конструктора имеет параметр отслеживания со значением по умолчанию, которое отключает шаблон, когда его подпись слишком близка к подписям автоматически определенных нешаблонных копий и/или конструкторов движений base_from_member
.)
На более ранних стандартных компиляторах есть конструктор по умолчанию и несколько шаблонов членов конструктора. Эти шаблоны конструкторов могут принимать как можно больше аргументов (в настоящее время до десяти) и передавать их конструктору элемента данных.
Специализация для членских ссылок предлагает один конструктор, принимающий MemberType&
, что является единственным способом инициализации ссылки.
Поскольку C++ не позволяет явно указать параметры шаблона шаблонного конструктора, убедитесь, что аргументы уже близки к фактическому типу, используемому в желаемом конструкторе элемента данных. Может потребоваться явная конверсия.
Макроконстанта BOOST_BASE_FROM_MEMBER_MAX_ARITY
определяет максимальную длину аргумента для шаблонов конструктора. Константа может быть переопределена, если требуется больше (или меньше) конфигураций аргументов. Константа может быть прочитана для кода, который может быть расширен, как шаблон класса, и должен поддерживать тот же максимальный размер. (Примером кода будет класс, который использует этот шаблон класса в качестве базового класса для члена с гибким набором конструкторов.) Эта константа игнорируется при наличии функций C++11.
В стартовом примере подобъект fdoutbuf
должен быть инкапсулирован в базовом классе, который является неповрежденным до std::ostream
.
#include <boost/utility/base_from_member.hpp>
#include <streambuf>
#include <ostream>
class fdoutbuf
: public std::streambuf
{
public:
explicit fdoutbuf( int fd );
};
class fdostream
: private boost::base_from_member<fdoutbuf>
, public std::ostream
{
typedef boost::base_from_member<fdoutbuf> pbase_type;
typedef std::ostream base_type;
public:
explicit fdostream( int fd )
: pbase_type( fd ), base_type( &member ){}
};
Идиома «база-от-член» является деталью реализации, поэтому она не должна быть видна клиентам (или любым производным классам) fdostream
. Из-за порядка инициализации подобъект fdoutbuf
будет инициализирован до того, как std::ostream
подобъект сделает первый подобъект безопасным для использования в конструкции последнего подобъекта. Поскольку подобъектом конечного типа fdoutbuf
является единственный подобъект с именем member
, это имя может быть использовано неквалифицированно в рамках конечного класса.
Шаблоны класса «база-из-члена» обычно должны включать только один подобъект «база-из-члена», обычно для присоединения потокового буфера к потоку ввода/вывода. Следующий пример показывает, как использовать несколько подобъектов базы из членов и возникающие проблемы с квалификацией.
#include <boost/utility/base_from_member.hpp>
#include <cstddef>
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>
{
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.y )
, pbase3_type( x, static_cast<int *>(NULL) )
, base1_type( pbase3_type::member, pbase1_type::member )
, base2_type( pbase2_type::member )
{
}
Окончательный класс имеет несколько подобъектов с именем member
, поэтому любое использование этого имени требует квалификации по имени соответствующего базового типа. (Использование typedef
облегчает упоминание базовых типов.) Однако исправление вводит новую проблему, когда нужен указатель. Использование адресного оператора с подобъектом, квалифицированным по названию его класса, приводит к появлению указателя на член (здесь, имеющий тип an_int base_from_member< an_int, > ::
) вместо указателя на член (имеющий тип an_int
>). Новая задача фиксируется путем квалификации подобъекта с this->
и нужна только для указателей, а не для ссылок или значений.
В инициализации есть некоторые преобразования аргументов. Аргумент конструктора для pbase0_type
преобразуется из double
в float
. Первый аргумент конструктора для pbase2_type
преобразуется из int
в double
. Второй аргумент конструктора для pbase3_type
является особым случаем необходимого преобразования; все формы буквального нуль-пойнтера в C++ (кроме nullptr
из C++11) также выглядят как интегральные выражения времени компиляции, поэтому C++ всегда интерпретирует такой код как целое число, когда у него есть перегрузки, которые могут принимать либо целое число, либо указатель. Последнее преобразование необходимо для вызова компилятором формы конструктора с точным типом указателя, используемым в конструкторе switcher
. (Если используется C++11 nullptr
, ему все равно необходимо преобразование, если несколько типов указателей могут быть приняты в вызове конструктора, но std::nullptr_t
не может.)