更多 C++ 慣用法/從成員派生
從派生類的 datamember 初始化基類。
在 C++ 中,基類在派生類成員之前初始化。這樣做是因為派生類成員可以使用物件的基部分。因此,所有基部分(即所有基類)必須在派生類成員之前初始化。但是,有時需要從僅在派生類中可用的 datamember 初始化基類。這聽起來與 C++ 語言的規則相矛盾,因為傳遞給基類建構函式的引數(派生類成員)必須完全初始化。這會導致迴圈初始化問題(無限迴歸)。
以下程式碼是從 Boost 庫 獲取的,[1] 顯示了這個問題。
#include <streambuf>
#include <ostream>
namespace std {
class streambuf;
class ostream {
explicit ostream(std::streambuf * buf);
//...
};
}
// A customization of streambuf.
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 )
// This is not allowed: buf can't be initialized before std::ostream.
// std::ostream needs a std::streambuf object defined inside fdoutbuf.
{}
};
上面的程式碼片段展示了程式設計師想要自定義 std::streambuf 類的情況。他們透過從 std::streambuf 繼承在 fdoutbuf 中這樣做。fdoutbuf 類用作 fdostream 類的成員,它是一種 std::ostream 類。但是,std::ostream 類需要指向 std::streambuf 類或其派生類的指標。指向 buf 的指標型別是合適的,但是隻有在 buf 被初始化的情況下傳遞它才有意義。但是,除非所有基類都被初始化,否則它不會被初始化。因此產生了無限迴歸。從成員派生慣用法解決了這個問題。
這個慣用法利用了基類按宣告順序初始化的事實。派生類控制其基類的順序,進而控制它們的初始化順序。在這個慣用法中,添加了一個新類,專門用來初始化導致問題的派生類中的成員。這個新類在所有其他基類之前被引入到基類列表中。由於新類在需要完全構造引數的基類之前,它先被初始化,然後引用可以像往常一樣被傳遞。以下是使用從成員派生慣用法進行的解決方案。
#include <streambuf>
#include <ostream>
class fdoutbuf : public std::streambuf
{
public:
explicit fdoutbuf(int fd);
//...
};
struct fdostream_pbase // A newly introduced class.
{
fdoutbuf sbuffer; // The member moved 'up' the hierarchy.
explicit fdostream_pbase(int fd)
: sbuffer(fd)
{}
};
class fdostream
: protected fdostream_pbase // This class will be initialized before the next one.
, public std::ostream
{
public:
explicit fdostream(int fd)
: fdostream_pbase(fd), // Initialize the newly added base before std::ostream.
std::ostream(&sbuffer) // Now safe to pass the pointer.
{}
//...
};
int main()
{
fdostream standard_out(1);
standard_out << "Hello, World\n";
return 0;
}
fdostream_pbase 類是新引入的類,它現在具有 sbuffer 成員。fdostream 類從這個新類繼承,並在其基類列表中將其新增到 std::ostream 之前。這確保了 sbuffer 在 std::ostream 之前被初始化,並且指標可以安全地傳遞給它的建構函式。