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

已知應用

編輯

相關的慣用語

編輯

參考

編輯
  1. Boost Utility http://www.boost.org/doc/libs/1_61_0/libs/utility/doc/html/base_from_member.html