Base-from-Member 惯用法
类模板 boost::base_from_member
为那些需要用一个成员对基类进行初始化的类提供了解决的方法。这个类模板位于 boost/utility/base_from_member.hpp,该文件被 boost/utility.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 ) {} //... };
其它项目也可以使用类似的定制基类。这个技巧非常基本,应该用一个模板来提供。主要的模板参数是被包含的成员的类型。该模板类有几个(显式的)构造函数成员模板,用于将构造函数的参数传递给成员。该模板类使用隐式的复制构造和赋值操作,如果被包含的成员是不可复制的,则取消它们。
如果应用该模板的类需要很复杂的构造和复制操作,或者编译器不够先进而不太使用它,那么手工编写基类会更好些。
由于基类是没有名字的,所以一个类不能有多个(直接的)相同类型的基类。这个模板类有一个额外的整型模板参数,用于提供一个有区别的单独的类。这个参数有一个缺省值,如果你只有一个成员类型,就可以只写一个参数。
摘要
#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;
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 表示了作为基类的成员的类型。另一个模板参数 UniqueID 是一个整数,用于区分使用多个相同类型作为基类。后一个模板参数如果被忽略,其缺省值为0。这个类模板有一个保护数据成员,名为 member,可以被派生类用于后面的基类(或派生类自身)。
它有一个缺省构造函数和几个构造函数成员模板。这些构造函数模板可以接受多个参数(当前最大为10个)并传递给数据成员的构造函数。由于C++不允许显式声明模板构造函数的模板参数,所以要确保这些参数已经尽可能接近数据成员的构造函数所使用的真实类型。
BOOST_BASE_FROM_MEMBER_MAX_ARITY 宏常量指定了构造函数模板的最大参数长度。需要更多(或更少)参数配置时,这个常量可以被覆盖。这个常量会被可扩展的代码读出,必须维护一个相同的最大值。(例如,一个类将该类模板用作基类,其成员具有一组灵活的构造函数)。
用法
以开始的例子为例,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" 的子对象,所以这个名字可以被最终类不加限定地使用。
例子
base-from-member 类模板通常只包含一个 base-from-member 子对象,象关联一个 stream-buffer 到一个 I/O 流。下面的例子示范了如何使用多个 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
可以更容易地引用基类名)。但是,这又会在需要使用指针时带来一个新的问题。对一个以类名限定的子对象使用地址操作符会得到一个成员指针(这里有一个类型 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
的构造函数。
Credits
Contributors
- Ed Brey
- Suggested some interface changes.
- R. Samuel Klatchko (rsk@moocat.org, rsk@brightmail.com)
- Invented the idiom of how to use a class member for initializing a base class.
- Dietmar Kuehl
- Popularized the base-from-member idiom in his 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 library.
- Daryle Walker
- Started the library. Contributed the test file base_from_member_test.cpp.
Revised: 28 August 2004
Copyright 2001, 2003, 2004 Daryle Walker. Use, modification, and distribution are subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or a copy at <http://www.boost.org/LICENSE_1_0.txt>.)