123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- [/
- Copyright 2001, 2003, 2004, 2012 Daryle Walker.
- Distributed under the Boost Software License, Version 1.0.
- See accompanying file LICENSE_1_0.txt
- or copy at http://boost.org/LICENSE_1_0.txt
- ]
- [article Base_From_Member
- [quickbook 1.5]
- [authors [Walker, Daryle]]
- [copyright 2001, 2003, 2004, 2012 Daryle Walker]
- [license
- Distributed under the Boost Software License, Version 1.0.
- (See accompanying file LICENSE_1_0.txt or copy at
- [@http://www.boost.org/LICENSE_1_0.txt])
- ]
- ]
- [section Rationale]
- When developing a class, sometimes a base class needs to be initialized
- with a member of the current class. As a na\u00EFve example:
- #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 ) {}
- //...
- };
- This is undefined because C++'s initialization order mandates that the base
- class is initialized before the member it uses. [@http://www.moocat.org R.
- Samuel Klatchko] developed a way around this by using the initialization
- order in his favor. Base classes are intialized in order of declaration, so
- moving the desired member to another base class, that is initialized before
- the desired base class, can ensure proper initialization.
- A custom base class can be made for this idiom:
- #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 ) {}
- //...
- };
- Other projects can use similar custom base classes. The technique is basic
- enough to make a template, with a sample template class in this library.
- The main template parameter is the type of the enclosed member. The
- template class has several (explicit) constructor member templates, which
- implicitly type the constructor arguments and pass them to the member. The
- template class uses implicit copy construction and assignment, cancelling
- them if the enclosed member is non-copyable.
- 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.
- [endsect]
- [section Synopsis]
- #include <type_traits> /* exposition only */
- #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;
- };
- The class template has a first template parameter `MemberType` representing
- the type of the based-member. It has a last template parameter `UniqueID`,
- that is an `int`, to differentiate between multiple base classes that use
- the same based-member type. The last template parameter has a default value
- of zero if it is omitted. The class template has a protected data member
- called `member` that the derived class can use for later base classes (or
- itself).
- If the appropriate features of C++11 are present, there will be a single
- constructor template. It implements ['perfect forwarding] to the best
- constructor call of `member` (if any). The constructor template is marked
- both `constexpr` and `explicit`. The former will be ignored if the
- corresponding inner constructor call (of `member`) does not have the marker.
- The latter binds the other way; always taking effect, even when the inner
- constructor call does not have the marker. The constructor template
- propagates the `noexcept` status of the inner constructor call. (The
- constructor template has a trailing parameter with a default value that
- disables the template when its signature is too close to the signatures of
- the automatically-defined non-template copy- and/or move-constructors of
- `base_from_member`.)
- On earlier-standard compilers, there is a default constructor and several
- constructor member templates. These constructor templates can take as many
- arguments (currently up to ten) as possible and pass them to a constructor
- of the data member.
- A specialization for member references offers a single constructor taking
- a `MemberType&`, which is the only way to initialize a reference.
- Since C++ does not allow any way to explicitly state the template parameters
- of a templated constructor, make sure that the arguments are already close
- as possible to the actual type used in the data member's desired constructor.
- Explicit conversions may be necessary.
- The `BOOST_BASE_FROM_MEMBER_MAX_ARITY` macro constant specifies the maximum
- argument length for the constructor templates. The constant may be overridden
- if more (or less) argument configurations are needed. The constant may be
- read for code that is expandable like the class template and needs to
- maintain the same maximum size. (Example code would be a class that uses
- this class template as a base class for a member with a flexible set of
- constructors.) This constant is ignored when C++11 features are present.
- [endsect]
- [section Usage]
- With the starting example, the `fdoutbuf` sub-object needs to be
- encapsulated in a base class that is inheirited before `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 ){}
- //...
- };
- The base-from-member idiom is an implementation detail, so it should not
- be visible to the clients (or any derived classes) of `fdostream`. Due to
- the initialization order, the `fdoutbuf` sub-object will get initialized
- before the `std::ostream` sub-object does, making the former sub-object
- safe to use in the latter sub-object's construction. Since the `fdoutbuf`
- sub-object of the final type is the only sub-object with the name `member`
- that name can be used unqualified within the final class.
- [endsect]
- [section Example]
- The base-from-member class templates should commonly involve only one
- base-from-member sub-object, usually for attaching a stream-buffer to an
- I/O stream. The next example demonstrates how to use multiple
- base-from-member sub-objects and the resulting qualification issues.
- #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.y )
- , pbase3_type( x, static_cast<int *>(NULL) )
- , base1_type( pbase3_type::member, pbase1_type::member )
- , base2_type( pbase2_type::member )
- {
- //...
- }
- The final class has multiple sub-objects with the name `member`, so any
- use of that name needs qualification by a name of the appropriate base
- type. (Using `typedef`s ease mentioning the base types.) However, the fix
- introduces a new problem when a pointer is needed. Using the address
- operator with a sub-object qualified with its class's name results in a
- pointer-to-member (here, having a type of `an_int boost::base_from_member<
- an_int, 0> :: *`) instead of a pointer to the member (having a type of
- `an_int *`). The new problem is fixed by qualifying the sub-object with
- `this->` and is needed just for pointers, and not for references or values.
- There are some argument conversions in the initialization. The constructor
- argument for `pbase0_type` is converted from `double` to `float`. The first
- constructor argument for `pbase2_type` is converted from `int` to `double`.
- The second constructor argument for `pbase3_type` is a special case of
- necessary conversion; all forms of the null-pointer literal in C++ (except
- `nullptr` from C++11) also look like compile-time integral expressions, so
- C++ always interprets such code as an integer when it has overloads that can
- take either an integer or a pointer. The last conversion is necessary for the
- compiler to call a constructor form with the exact pointer type used in
- `switcher`'s constructor. (If C++11's `nullptr` is used, it still needs a
- conversion if multiple pointer types can be accepted in a constructor call
- but `std::nullptr_t` cannot.)
- [endsect]
- [section Acknowledgments]
- * [@http://www.boost.org/people/ed_brey.htm Ed Brey] suggested some interface
- changes.
- * [@http://www.moocat.org R. Samuel Klatchko] ([@mailto:rsk@moocat.org
- rsk@moocat.org], [@mailto:rsk@brightmail.com rsk@brightmail.com]) invented
- the idiom of how to use a class member for initializing a base class.
- * [@http://www.boost.org/people/dietmar_kuehl.htm Dietmar Kuehl] popularized the
- base-from-member idiom in his [@http://www.informatik.uni-konstanz.de/~kuehl/c++/iostream/
- IOStream example classes].
- * Jonathan Turkanis supplied an implementation of generating the constructor
- templates that can be controlled and automated with macros. The
- implementation uses the [@../../../preprocessor/index.html Preprocessor library].
- * [@http://www.boost.org/people/daryle_walker.html">Daryle Walker] started the
- library. Contributed the test file [@../../test/base_from_member_test.cpp
- base_from_member_test.cpp].
- [endsect]
|