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
建構子。