C++ BoostBase-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>.)