More C++ Idioms/父类别是资料成员
父类别是资料成员(Base-from-Member)
编辑
目的
编辑在子类别初始化位在资料成员的父类别
别名
编辑动机
编辑在C++,父类别的初始化是优先于子类别的任意资料成员。这是因为子类别的成员可能使用到物件的基础部分。 因此,在初始化子类别的资料成员,所有的基础部分(例如:所有父类别)必须被初始化。然而,有时侯从资料成员初始化父类别是有必要,此方法只可以在子类别。 然而,它与c++语言规则是相互矛盾,因为参数(子类别的一个资料成员)被传递到父类别时,必须已经被完全地被初始化。这样将产生循环的初始化问题。(an infinite regress)。
以下Boost函式库[1]范例程式码说明该问题。
#include <streambuf> // 為了std::streambuf
#include <ostream> // 為了std::ostream
class fdoutbuf
: public std::streambuf
{
public:
explicit fdoutbuf( int fd );
//...
};
class fdostream // 客製化的streambuf
: public std::ostream
{
protected:
fdoutbuf buf;
public:
explicit fdostream( int fd )
: buf( fd ), std::ostream( &buf )
// 這是不允許的:成員變數buf不能在std::ostream之前被初始化。
// std::ostream 需要一個物件std::streambuf,此物件被定義成員變數buf裡。
{}
//...
};
以上的程式码片断解释一个例子,当程式设计者对客制化类别 std::streambuf
有兴趣。他/她,所以让资料成员fdoutbuf
继承类别std::streambuf
。类别fdoutbuf
被当作在类别fdostream
的资料成员,fdoutbuf
是(is-a)std::ostream
的同类型。然而,类别需要指向类别或它的子类别。指向buf
的指标类型是合适的,但指标只能在buf
已经初始化下才能传递,这样才合乎常理。然而,除非所有父类别已经初始化,否则此指标不能被初始化。因此是无限回归。 父类别是资料成员(Base-from-Member)惯用语强调此问题。
解决方案与范例程式
编辑这个惯用语法利用父类别依据宣告变数顺序初始化。子类别控制它的父类别的顺序,依序控制父类别初始化的顺序。在这个惯用语,一个新类别被加到子类别的资料成员进行初始化,这将引起问题。这个新类别必须加入在所有其他父类别列表(other base classes)之前。 因为新类别在父类别之前需要完全地建构参数,它先被初始化然后其他指标能照常传递。 因此解决方案是父类别取自成员的惯用语。
#include <streambuf> // 為了std::streambuf
#include <ostream> // 為了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
: protected fdostream_pbase // 此父類別將在下一個類別之前被初始化。
, public std::ostream
{
public:
explicit fdostream(int fd)
: fdostream_pbase(fd), // 在std::ostream之前,初始化新加入的父類別。
std::ostream(&sbuffer) // 現在安全傳遞指標
{}
//...
};
int main(void)
{
fdostream standard_out(1);
standard_out << "Hello, World\n";
return 0;
}
类别fdostream_pbase
是新加入的类别,现在此类别有sbuffer
的资料成员。类别fdostream
继承这个新类别,并且在他的基底类别列表将它 <加入在std::ostream
之前。 这是为了确保sbuffer
能被先初始化 ,然后指标能安全传递到std::ostream
建构子。