Why C++ Has Multiple Constructors (C1, C2, C3) — Just Like Destructors
In the previous article, we explored how a single virtual destructor in C++ can result in multiple destructor variants being generated by…
Why C++ Has Multiple Constructors (C1, C2, C3) — Just Like Destructors
In the previous article, we explored how a single virtual destructor in C++ can result in multiple destructor variants being generated by the compiler.
Naturally, this raises a question:
If destructors have multiple variants, what about constructors?
The answer is: exactly the same idea applies.
Under the Itanium C++ ABI (used by GCC and Clang), constructors also come in multiple forms to handle inheritance, virtual bases, and object layout correctly.
In assembly, you’ll typically see them as:
- C1 — Complete Object Constructor
- C2 — Base Object Constructor
- C3 — Complete Object Allocating Constructor (less common)
Let’s break them down.
1. Complete Object Constructor (C1)
This is what most developers think of as “the constructor.”
What it does
- Initializes the entire object
- Constructs:
- all base classes (including virtual bases)
- all member variables
- Installs the appropriate vptrs (including construction vtables during initialization)
When it’s used
Whenever you create a complete object, for example:
C c; // stack allocation
C* p = new C; // heap allocation (after operator new)
The complete object constructor (C1) is used to initialize the full object after memory has been obtained.
2. Base Object Constructor (C2)
This is where things get more interesting.
The C2 constructor is used when a class is being constructed as a subobject of another class.
What it does
- Initializes:
- the class’s own members
- its non-virtual base classes
- Does NOT initialize virtual base classes because virtual base initialization is deferred to the most-derived constructor (C1)
Note: From an ABI perspective, C1 and C2 are often implemented as separate symbols, but compilers may share implementations internally or emit one as a thin wrapper around the other.
Why this distinction exists
Consider the diamond inheritance again:
class VBase {
public:
virtual ~VBase() = default;
};
class A : virtual public VBase {};
class B : virtual public VBase {};
class C : public A, public B {};
If both A and B tried to fully construct VBase, you would get double initialization — which is undefined behavior.
So the rule is:
Only the most-derived class C constructs virtual bases.
This means:
Cuses C1 → constructs everything (includingVBase)AandB, when constructed insideC, use C2 → skipVBase
When C2 is used
When a derived class constructs its base classes:
C::C() {
// internally calls:
// B::B() [C2]
// A::A() [C2]
}
So C2 is never used directly by user code — it is part of the compiler’s internal orchestration.
The distinction between C1 and C2 reflects whether the constructor is operating on the most-derived object or on a base subobject within a larger object.
3. Complete Object Allocating Constructor (C3)
This is the rarest variant and is largely a theoretical optimization hook in the ABI.
What it does:
- Allocates memory (calls
operator new). - Then performs the construction (like C1).
Note: While defined in the ABI, most modern compilers (like Clang and GCC) omit this. They prefer the transparency of calling operator new followed by the C1 constructor. You will rarely see a _ZN...C3... symbol in a modern binary, but it exists in the specification for potential future optimizations.
Where This Connects to VTT
As discussed in my previous article, if you’ve seen something like this in assembly:
movabs rsi, VTT for C
This typically occurs when invoking base object constructors (C2) in classes with virtual inheritance.
When constructing base subobjects in a class with virtual inheritance, the compiler passes a pointer to the VTT (Virtual Table Table).
This tells the constructor:
“You are being constructed as part of a larger object — use the correct construction vtables.”
Without this, the constructor might:
- install the wrong
vptr - assume incorrect virtual base layout
- break polymorphic behavior during construction
The Big Picture
Just like destructors, constructors must handle multiple contexts:
- constructing a standalone object
- constructing a subobject
- constructing with virtual inheritance
- optionally combining allocation + construction
Instead of adding complex logic at every call site, the ABI defines separate constructor variants, each responsible for a specific scenario.
Final Insight
The key idea is the same as with destructors:
The compiler splits object lifetime operations into specialized variants so each function has a well-defined responsibility.
This design:
- simplifies code generation
- avoids duplication of work (like virtual base initialization)
- ensures correctness in complex inheritance hierarchies
It’s another example of how C++ hides significant complexity behind a simple surface-level syntax — while still generating highly efficient code.
Thanks for reading!
If you enjoy exploring C++ internals or C++ in general feel free to connect with me:
LinkedIn: https://linkedin.com/in/rabulyaev GitHub: https://github.com/abulyaev
메타데이터
- post_id
- ed4b5c4e20ed
- slug
- why-c-has-multiple-constructors-c1-c2-c3-just-like-destructors-ed4b5c4e20ed
- url
- https://medium.com/@abulyaev/why-c-has-multiple-constructors-c1-c2-c3-just-like-destructors-ed4b5c4e20ed
- canonical_url
- https://medium.com/@abulyaev/why-c-has-multiple-constructors-c1-c2-c3-just-like-destructors-ed4b5c4e20ed
- author_url
- https://medium.com/@abulyaev
- status
- ok
- fetched_at
- 2026-06-23 03:48:11