Defaulted vs Implicit Constructors in C++
In C++, both implicitly generated constructors and explicitly
defaulted constructors (= default) allow the compiler to
synthesize baseline member initialization logic. While they often
produce identical machine code, they differ in critical ways regarding
access control, triviality, user-declared status, aggregate
classification, and definition placement. Understanding these
distinctions is essential for designing robust types that interact
correctly with type traits, overload resolution, and initialization
rules.
User-Declared Status and Other Special Member Functions
An implicitly generated default constructor only exists if no other constructors are declared by the user. If you declare any parameterized constructor, the compiler suppresses the implicit default constructor entirely.
An explicitly defaulted constructor (T() = default;) is
considered a user-declared constructor. Declaring it explicitly
forces the constructor into existence even when parameterized
constructors are present. However, because it is user-declared, it
impacts how the compiler interprets the class:
- In standards prior to C++20, declaring any constructor—even
= default—disqualified a class from being an aggregate type. In C++20 and later, an explicitly defaulted constructor is permitted in an aggregate, provided it is not user-provided. - An explicitly defaulted constructor communicates design intent clearly, assuring both the compiler and maintainers that default construction is deliberately supported.
Access Control Customization
Implicitly generated constructors are always generated with
public access. The compiler cannot automatically place an
implicit constructor in a protected or private
scope.
An explicitly defaulted constructor can be placed under any access specifier:
protected: Useful for base classes that should not be directly instantiated on their own, but need compiler-generated construction logic available to derived classes.private: Useful for patterns such as singletons or factory-based creation methods where external instantiation must be prohibited while retaining compiler-generated initialization.
In-Class vs. Out-of-Line Definitions and Triviality
An implicitly generated constructor is always defined inline where the class definition resides. Its triviality depends strictly on whether the class's base classes and non-static data members have trivial default constructors.
An explicitly defaulted constructor can be defaulted either inside the class definition or out-of-line:
- Defaulted inside the class definition
(
T() = default;): It is treated as non-user-provided. If the members and base classes allow it, the constructor remains trivial. - Defaulted out-of-line
(
T::T() = default;): It is treated as user-provided. A user-provided constructor is never trivial, even if it uses= default. This alters traits likestd::is_trivialandstd::is_trivially_default_constructible, affecting whether objects of the type can be manipulated via raw memory operations such asstd::memcpy. - Header decoupling: Out-of-line defaulting allows
you to declare a defaulted constructor in a header file and define it in
an implementation file (
.cpp). This is especially valuable when managing types that hold incomplete types viastd::unique_ptr(Pimpl idiom), where the destructor or constructor must be instantiated only where the complete type definition is visible.
Exception Specifications and Attributes
An implicitly generated constructor automatically deduces its
noexcept status based on whether all base and member
operations invoked are non-throwing. It also automatically deduces
constexpr readiness if the operations satisfy
constexpr requirements.
With explicit defaulting:
- You can explicitly attach qualifiers such as
constexpror explicit exception specifications (e.g.,constexpr T() noexcept = default;). - If you supply an explicit
noexcept(true)specification on an explicitly defaulted constructor and a member initialization could actually throw, the compiler will flag this mismatch as a compile-time error, giving you a mechanism to strictly enforce compile-time contracts.