← Back to list

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…

abulyaev · 2026-03-28 17:31 · 0 claps · 2.9 min read
#cpp #cpp-programming #tech #software-development #software-engineering
Open on Medium ↗
Wiki topics: 💻 · Programming

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:

  • C uses C1 → constructs everything (including VBase)
  • A and B, when constructed inside C, use C2 → skip VBase

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