Skip to content
C++
Idiom
since C++98
Intermediate

Base From Member

Guarantee a member object is fully constructed before a dependent base class by promoting it into an earlier base class.

Base From Membersince C++98

A technique that resolves the base-before-member initialization constraint by promoting a data member into a dedicated private base class, ensuring it is fully constructed before any dependent base that requires it.

Overview

C++ imposes a fixed initialization order within a class: every base class subobject is fully constructed before any non-static data member, and bases are initialized in the order they appear in the base-class list β€” not the order written in the member-initializer list. This rule is immutable and cannot be worked around by rearranging the initializers.

The problem surfaces when a base class constructor requires a resource β€” a buffer, a policy object, a handle β€” that the derived class also needs to own. By the time the base is being initialized, the derived class's members do not yet exist.

cpp
// BROKEN β€” streambuf_ does not exist when basic_ios is constructed
class LogStream : public std::basic_ios<char> {
    std::stringbuf streambuf_;   // initialized AFTER basic_ios
public:
    LogStream()
        : std::basic_ios<char>(&streambuf_)  // UB: &streambuf_ is garbage
        , streambuf_{}
    {}
};

The Base From Member idiom resolves this by making the member a base class. Because bases are initialized in declaration order, declaring the carrier before the dependent base guarantees the resource exists when needed.

cpp
// Fixed β€” SbufBase is initialized first, basic_ios sees a valid pointer
struct SbufBase {
    std::stringbuf buf;
};

class LogStream
    : private SbufBase          // initialized 1st
    , public  std::basic_ios<char>  // initialized 2nd β€” buf is alive
{
public:
    LogStream()
        : SbufBase{}
        , std::basic_ios<char>(&buf)  // safe
    {}
};

The Standard Library itself is built on this pattern. Implementations of std::basic_stringstream must initialize a std::basic_stringbuf before the std::basic_ios subobject that stores a pointer to it. Every conforming implementation uses a carrier base, often named __str_buf_base or similar, declared ahead of the stream base in the hierarchy.

Syntax

The canonical shape is a two-level inheritance chain where the carrier comes first:

cpp
// Carrier β€” holds the resource, no other responsibilities
template<typename T>
struct BaseMember {
    explicit BaseMember(auto&&... args)
        : value(std::forward<decltype(args)>(args)...)  // C++11 perfect forward
    {}
    T value;
};

// Derived class β€” carrier precedes every base that depends on value
class MyClass
    : private BaseMember<Resource>  // initialized 1st
    , public  DependentBase         // initialized 2nd
{
public:
    explicit MyClass(Resource r)
        : BaseMember<Resource>(std::move(r))          // C++11 std::move
        , DependentBase(BaseMember<Resource>::value)  // safe: resource is alive
    {}
};

The carrier is almost always private: it is a construction artefact, not part of the public interface. Access from within the derived class uses the qualified name (BaseMember<T>::value) to avoid ambiguity if the derived class also defines a member with the same name.

Examples

Custom stream with an owned buffer

cpp
#include <ostream>
#include <streambuf>
#include <array>
#include <string_view>   // C++17

class FixedBuf : public std::streambuf {
    std::array<char, 4096> storage_;
public:
    FixedBuf() { setp(storage_.data(), storage_.data() + storage_.size()); }

    std::string_view view() const {   // C++17
        return {pbase(), static_cast<std::size_t>(pptr() - pbase())};
    }
};

// Carrier base
struct FixedBufBase {
    FixedBuf buf;
};

// FixedStream owns its buffer with no external lifetime dependency
class FixedStream
    : private FixedBufBase       // must precede ostream β€” base-from-member
    , public  std::ostream
{
public:
    FixedStream() : FixedBufBase{}, std::ostream(&buf) {}

    std::string_view view() const { return buf.view(); }  // C++17
};

int main() {
    FixedStream fs;
    fs << "status=" << 42 << " ok\n";
    auto s = fs.view();   // "status=42 ok\n"
}

File-backed logger β€” handle open before base hooks fire

Some base classes invoke virtual functions or register callbacks during their own constructor. If those callbacks need a valid resource, the carrier must guarantee the resource exists before the base constructor body runs.

cpp
#include <cstdio>
#include <stdexcept>

// Non-copyable, non-movable handle carrier
struct FileHandleBase {
    explicit FileHandleBase(const char* path)
        : fp(std::fopen(path, "w"))
    {
        if (!fp) throw std::runtime_error("cannot open log file");
    }
    ~FileHandleBase() { if (fp) std::fclose(fp); }

    FileHandleBase(const FileHandleBase&)            = delete;  // C++11
    FileHandleBase& operator=(const FileHandleBase&) = delete;  // C++11

    std::FILE* fp;
};

// LoggerBase registers itself on construction and calls open_hook()
struct LoggerBase {
    explicit LoggerBase(std::FILE* out);  // stores out, calls open_hook()
    virtual void open_hook() {}
    virtual ~LoggerBase() = default;      // C++11
};

class FileLogger
    : private FileHandleBase  // fp is valid before LoggerBase::LoggerBase runs
    , public  LoggerBase
{
public:
    explicit FileLogger(const char* path)
        : FileHandleBase(path)
        , LoggerBase(FileHandleBase::fp)  // safe
    {}
};

Multiple carriers of the same type

When two members share a type, two identically typed carrier bases create ambiguity. Use a discriminator to distinguish them:

cpp
template<typename T, int N = 0>
struct TaggedBase {
    T value;
};

class DualBufferStream
    : private TaggedBase<std::stringbuf, 0>  // input buffer
    , private TaggedBase<std::stringbuf, 1>  // output buffer
    , public  std::iostream
{
    using InBase  = TaggedBase<std::stringbuf, 0>;
    using OutBase = TaggedBase<std::stringbuf, 1>;
public:
    DualBufferStream()
        : InBase{}, OutBase{}
        , std::iostream(nullptr)
    {
        set_rdbuf(nullptr); // configure separately after construction
    }
};

Boost provides boost::base_from_member<T, N> as a production-ready version of this pattern, with the same discriminator semantics.

Best Practices

Name carriers after what they hold. StringBufBase or HandleBase is immediately navigable; MemberBase or Base1 collides with every other use of the idiom in the codebase.

Always declare the carrier private. It is an initialization artefact. Exposing it as a public or protected base leaks internal structure and may enable unintended slicing or casting.

Consider [[no_unique_address]] for stateless carriers (C++20). When the promoted member is a zero-size policy type, making it a base exploits the Empty Base Optimization but forces inheritance. C++20 [[no_unique_address]] achieves identical space savings as a member while preserving ordinary member-initialization order within a class β€” members initialize in declaration order, so a zero-size [[no_unique_address]] policy member declared before a dependent member works without inheritance.

cpp
// C++20 β€” no base class needed when the policy is stateless
struct EmptyPolicy {};

class Thing {
    [[no_unique_address]] EmptyPolicy policy_;  // C++20 β€” zero size
    DependentMember dep_;                       // initialized after policy_
public:
    Thing() : policy_{}, dep_{policy_} {}
};

For non-empty carriers, the inheritance-based form remains the correct solution.

Comment the ordering constraint. The declaration order in the base-class list is load-bearing, but nothing in the syntax signals this to a reader. A single inline comment prevents silent regressions from well-meaning reformatters.

cpp
class MyStream
    : private BufBase           // must precede ostream β€” base-from-member
    , public  std::ostream

Common Pitfalls

Wrong base order. Swapping the carrier and the dependent base compiles without diagnostic in most cases and silently produces undefined behaviour: the dependent base constructor receives a pointer or reference to an object that has not yet begun its lifetime. AddressSanitizer and UBSan catch this at runtime; no static warning is guaranteed.

Virtual bases break the guarantee. If the dependent base is also a virtual base, the most-derived class is responsible for initializing it, not the intermediate derived class. The carrier trick cannot push initialization earlier in that scenario; the design must be restructured.

Destructors consume the promoted member. Destruction proceeds in reverse construction order: the dependent base destructor runs before the carrier destructor. If the dependent base's destructor flushes to the buffer (as std::ostream::~basic_ostream does), the buffer is still alive β€” the ordering is correct. Verify this invariant holds whenever you modify the class hierarchy.

Unqualified member access. If the derived class declares a member with the same name as the carrier's field, unqualified references inside the class resolve to the derived member. Always use a qualified name (InBase::value) or a protected accessor to reference the promoted member.

See Also

  • std::basic_ios and std::basic_streambuf in <ios> β€” the Standard Library's canonical use of this idiom
  • Empty Base Optimization (EBO) β€” related technique that eliminates the size overhead of stateless base classes
  • [[no_unique_address]] (C++20, <type_traits> context) β€” zero-size member alternative for stateless policies
  • boost::base_from_member<T, N> in <boost/utility/base_from_member.hpp> β€” reusable parameterized carrier with discriminator support