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