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++98A 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.
// 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.
// 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:
// 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
#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.
#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:
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.
// 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.
class MyStream
: private BufBase // must precede ostream β base-from-member
, public std::ostreamCommon 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_iosandstd::basic_streambufin<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 policiesboost::base_from_member<T, N>in<boost/utility/base_from_member.hpp>β reusable parameterized carrier with discriminator support