Skip to content
VLSI Mentor

C++ Part I · Module 12 · Constructors and Destructors

Default, Defaulted and Deleted Constructors

Writing no constructor is not the absence of a decision. The compiler makes several on your behalf, and they are real functions with observable behaviour — Chapter 12.1 already measured one of them leaving an int member holding a leftover value with no warning issued. There are five such compiler-written functions in total, they appear and disappear according to rules that are much better known than discovered, and one of those rules has already caused something in this curriculum: declaring a single constructor of your own silently removes the one that takes no arguments, which is exactly why Chapter 11.1's Field h; refused to compile. This chapter is about controlling that set deliberately. It covers how to ask for a compiler-written function back after your own constructor has displaced it, which looks like a formality and turns out to differ from the obvious alternative in a way that is measurable in three separate type properties and in whether brace initialization still works at all. And it covers the opposite move — making a function exist specifically so that any attempt to call it is a compile error, which is the mechanism behind the two deleted lines that Chapter 10.8's buffer class needed and could not yet explain, and which has a second use that is less well known and more often the right tool.

Intermediate17 min readC++Part IFundamentalsConstructors

Part I · Module 12 · Chapter 12.4 · Default, Defaulted and Deleted Constructors

1. What You Get Without Asking

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct Plain { int i; };

That type has five member functions nobody wrote. Measured with the standard library's type traits — a measuring instrument here, and Module 21's subject:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Plain:
  default constructible : 1
  copy constructible    : 1
  copy assignable       : 1
  destructible          : 1
  move constructible    : 1

The five special member functions: a default constructor, a copy constructor, a copy assignment operator, a destructor, and (with its assignment partner) move operations. This chapter is about the default constructor; the copy and move ones are Module 13's subject and the destructor is Chapter 12.7's.

2. What the Implicit Default Constructor Does

Chapter 12.1 §4 measured it, and the result is worth repeating because it is the reason this chapter exists:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int i   = 1  (indeterminate?)
double d= 0.000000
string s= "" size 0
warnings: 0
MemberWhat the implicit default constructor did
int inothing — the 1 is leftover garbage
double dnothing — it happened to read 0.0
std::string sran its own default constructor

The implicit default constructor default-initializes each member: a constructor runs for members that have one, and built-in types are left alone.

It does not zero anything, and reading such a member is undefined — with 0 warnings, because from the compiler's view the object was constructed.

3. When It Disappears

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class WithCtor {
    public:
        explicit WithCtor(int v) : v_(v) {}
    private:
        int v_;
    };

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
WithCtor (has explicit WithCtor(int)):
  default constructible : 0   <-- gone
  copy constructible    : 1   <-- still there
  copy assignable       : 1   <-- still there

Declaring any constructor of your own removes the implicit default constructor — and only that one.

The copy and assignment functions are unaffected. This is the precise rule behind Chapter 11.1 §6's measurement, where Field h; gave error: no matching constructor for initialization of 'Field': Field had declared Field(int, int), so the no-argument constructor no longer existed.

That is usually what you want. Chapter 11.1 §6 argued it is the type being accurate — there is no such thing as a default register field. Chapter 12.2's callout said the same: do not invent a default state.

4. Asking for It Back

Sometimes a type genuinely has a valid default and other constructors.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Reg {
    public:
        Reg() = default;                       // give me the compiler's one back
        explicit Reg(int addr) : addr_(addr) {}
    private:
        int addr_ = 0;
    };

= default says: write the function you would have written if I had not interfered. It is a declaration, so it restores the member to the set.

5. = default Is Not the Same as {}

The obvious alternative is an empty body:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    Reg() {}            // looks equivalent
    Reg() = default;    // is not the same thing

Measured, on three otherwise-identical structs:

trivially_default_constructibleis_trivialis_aggregate
no constructor at all111
= default111
{}000

= default preserves everything; an empty body destroys all three. And the consequence that actually bites a caller:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct Defaulted { Defaulted() = default; int a; int b; };
    struct EmptyBody { EmptyBody() {}         int a; int b; };

    Defaulted d{3, 4};      // works
    EmptyBody e{1, 2};      // ?
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
brace init worked: i={1,2} d={3,4}
E e{1,2} with E(){} -> error: no matching constructor for initialization of 'E'

An empty body costs you aggregate initialization. Chapter 9.8 §2 defined an aggregate as a type with no user-provided constructor — and {} provides one while = default does not. Chapter 11.1 §6 measured the same boundary from the other side, where is_aggregate<PrivateOnly> was 0.

So = default is not a stylistic flourish. It is the only way to restore the member without changing what the type is.

(All three left the int indeterminate — measured as -273072040, 1 and -273071872. = default restores the compiler's behaviour faithfully, including the part you may not want; §9's habit is the answer.)

6. Where = default Is Used

To restore the default constructor after declaring another (§4).

To be explicit about the other four. Writing ~Reg() = default; or Reg(const Reg&) = default; documents that you considered them and chose the compiler's version. Module 13 is where that becomes a discipline rather than a habit, because declaring one of them affects whether others are generated.

And it can be written out of line, which lets a header declare it and a source file define it — occasionally useful for compile-time reasons.

7. = delete

The opposite: make the function exist and make calling it an error.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class NoCopy {
    public:
        NoCopy() = default;
        NoCopy(const NoCopy&) = delete;
    };

    NoCopy a;
    NoCopy b = a;        // ?
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: call to deleted constructor of 'NoCopy'
note: 'NoCopy' has been explicitly marked deleted here

This is the mechanism behind the two lines Chapter 10.8 §7's Buffer needed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    Buffer(const Buffer&)            = delete;
    Buffer& operator=(const Buffer&) = delete;

Chapter 10.8 flagged them as mandatory and deferred the reason: a copy would give two objects one pointer and therefore two destructors releasing the same memory, which is Chapter 10.7's double delete — measured there as exit 133, SIGTRAP. Deleting the copy operations makes that bug unexpressible. Chapter 13.4 is where the better answer lives, which is to define what a copy means rather than forbidding it.

Why = delete rather than making it private? The older technique was to declare a private copy constructor and never define it, which turned the error into a link failure. = delete makes it a compile error, at the call site, with the clear message above.

8. The Better-Known Use Is Not the Best One

= delete on an overload forbids a specific conversion:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Reg {
    public:
        explicit Reg(int) {}
        Reg(double) = delete;        // refuse doubles outright
    };

    Reg a(1);        // fine
    Reg b(1.5);      // ?
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: call to deleted constructor of 'Reg'
note: 'Reg' has been explicitly marked deleted here

A register address is not a floating-point quantity, and without the deleted overload Reg b(1.5) would silently convert 1.5 to 1 — Chapter 4.10's narrowing conversion, with the fraction discarded. The deleted overload participates in overload resolution, wins for double arguments, and then refuses.

This is the use most readers have not met, and it is the one that prevents bugs rather than merely forbidding operations. It pairs with Chapter 12.5's explicit: one stops an unwanted conversion into your type, the other stops an unwanted conversion of the argument.

9. When the Compiler Deletes It For You

Section 3 showed a user-declared constructor removing the default one. There is a second way it can disappear, and the compiler's wording for it is worth knowing because it connects §7 to §2.

A class whose member has no default constructor cannot have one either — there would be no way to bring that member into existence. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class NoDefault { public: explicit NoDefault(int v) : v_(v) {} private: int v_; };
    class Holder  { NoDefault m_{0}; };     // member has a default member initializer
    class Holder2 { NoDefault m_; };        // member has none
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
NoDefault default constructible : 0
Holder  (member has = {0})      : 1
Holder2 (member has no init)    : 0

Holder is default constructible and Holder2 is not, and the only difference is the {0}. The default member initializer supplies the argument the member's constructor needs, so the compiler can write a default constructor; without it, there is nothing to construct m_ from.

And the error when you try:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: call to implicitly-deleted default constructor of 'H'
note: default constructor of 'H' is implicitly deleted because field 'm_' has no default constructor

"Implicitly deleted" — the same word as §7's = delete. That is not a coincidence in the wording: the compiler's response to "I cannot write this function" is to define it as deleted, which is why the message says deleted rather than missing, and why the diagnostic looks like the one from an explicit deletion.

Two things follow.

The = default request can fail. Writing Holder2() = default; does not help — the compiler still cannot write it, so the function is still deleted. = default means write the one you would have written, and if that is nothing, you get nothing.

And this is Chapter 12.3 §7's three-impossible-cases rule seen from outside the class. There, a member with no default constructor forced the enclosing constructor to use the initializer list. Here, the same member removes the enclosing type's implicit constructor entirely — for the same reason, which is that the member cannot exist without a value.

The practical consequence is the one Chapter 11.1 §6 already measured as a feature rather than a problem: compose a type out of members that have real invariants, and the enclosing type stops being default-constructible too. That propagation is correct — if a Field has no valid default, neither does a Register that contains one.

10. What to Actually Do

Give every data member a default member initializer. It makes the implicit default constructor correct and costs nothing.

Let the default constructor vanish when there is no valid default state — that is the type being accurate.

Write = default rather than {} when you want the compiler's version back. Measured: {} loses triviality and aggregate initialization.

Use = default to document a deliberate choice about any of the five.

Use = delete on an overload to forbid a conversion that should never happen — Reg(double) = delete.

Prefer = delete to a private undefined declaration — a compile error at the call site beats a link error.

Treat deleting a copy constructor as a placeholder unless the type genuinely represents a unique claim.

And do not add a default constructor to make something else convenient — Chapter 12.3 §11 is the warning.

11. Mistakes Worth Making Once

Assuming the implicit default constructor zeroes members. Measured: 1, -273072040, -273071872.

Expecting a warning for reading them. 0 warnings.

Being surprised that Field h; stopped compiling after adding Field(int,int). That is §3's rule.

Writing {} instead of = default. Measured: three traits lost and E e{1,2} becomes error: no matching constructor.

Thinking = default and no-constructor-at-all differ. Measured identical in all three traits.

Declaring a private copy constructor instead of deleting it — a link error instead of a compile error.

Deleting copy operations and calling it done. Often Module 13's question deferred, not answered.

Not deleting a nonsensical overload. Reg b(1.5) silently truncates to 1.

Expecting a user constructor to remove the copy constructor. Measured: copy constructible stayed 1.

12. Interview Relevance

"What does the compiler generate for a class you write nothing in?" The five special members — default constructor, copy constructor, copy assignment, destructor, and the two move operations. Naming them is the baseline; saying what the default constructor does to members is the better answer.

"Why does adding a constructor break T t;?" Because declaring any constructor removes the implicit default one, and only that one. The measured trait table is the evidence.

"Is T() = default; the same as T() {}?" No, and this is an excellent discriminator. = default keeps the type trivially default constructible and an aggregate; {} makes it neither, so brace initialization stops compiling.

"Why = delete rather than a private declaration?" A compile error at the call site rather than a link error, with a clear message.

"Give a good use of = delete other than suppressing copies." Deleting an overload to forbid a conversion — Reg(double) = delete — which is the answer that shows real familiarity.

13. What You Should Be Able to Say Now

  1. A plain struct gets five special members — measured, all five present.
  2. The implicit default constructor default-initializes: a constructor for members that have one, nothing for built-ins.
  3. Measured: it left an int reading 1 and others reading -273072040 / -273071872, with 0 warnings.
  4. Declaring any constructor removes the implicit default one — and only that one. Measured: default 0, copy 1, copy-assign 1.
  5. That is exactly why Field h; failed to compile in Chapter 11.1 §6.
  6. = default restores it faithfully.
  7. Measured: = default ≠ {}. = default keeps trivially_default_constructible, is_trivial and is_aggregate at 1; {} makes all three 0.
  8. So {} loses brace initialization: E e{1,2} → error: no matching constructor for initialization of 'E'.
  9. = delete makes a function exist and uncallable — error: call to deleted constructor plus a note pointing at the deletion.
  10. That is the mechanism behind Chapter 10.8 §7's two lines, preventing Chapter 10.7's double delete.
  11. = delete on an overload forbids a conversion — Reg(double) = delete stops 1.5 silently becoming 1.
  12. Prefer = delete to a private undefined declaration — compile error, not link error.
  13. Default member initializers make the implicit default constructor correct for free.
  14. The five interact, and that is Module 13's Rules of Three, Five and Zero.

14. Exercises

Exercise 1 — what exists? For struct S { int a; std::string b; }; list the compiler-written members and say what the default constructor does to each data member.

Exercise 2 — predict. After adding explicit S(int), say which of S s;, S s(1);, S t = s; still compile.

Exercise 3 — the two spellings. Write a class needing both a default constructor and a one-argument one, using = default. Say what would change if you wrote {} instead, in three properties.

Exercise 4 — restore brace init. struct P { P() {} int x; int y; }; — P p{1,2} fails. Fix it with a one-word change and explain why it works.

Exercise 5 — delete the right thing. A Temperature class takes a value in Celsius as a double. Say which constructor you would delete to stop Temperature t(100) meaning something unintended, and why.

Exercise 6 — placeholder or permanent? Classify as a stopgap or a permanent design: deleting copies on a lock guard, on a buffer owning new[], on a register-field value type, on a file handle.

Exercise 7 — the cheap fix. Given class C { int a; double b; }; used as C c;, give the one-line-per-member change that makes it correct and say what you did not have to write.

Exercise 8 — read the note. Explain what note: 'NoCopy' has been explicitly marked deleted here is telling you and how it differs from "no such function".

15. Summary

  • A plain struct gets five special members free.
  • The implicit default constructor default-initializes — nothing for built-ins, their own constructor for class members.
  • Measured: garbage reads of 1, -273072040, -273071872, with 0 warnings.
  • Declaring any constructor removes only the implicit default one — the reason Field h; failed.
  • = default asks for it back, faithfully.
  • Measured: = default ≠ {} — {} loses trivially_default_constructible, is_trivial and is_aggregate, so E e{1,2} stops compiling.
  • = delete makes a function exist and uncallable, with a note pointing at the deletion.
  • That is Chapter 10.8's two lines, preventing Chapter 10.7's exit 133 double delete.
  • Deleting an overload forbids a conversion — the under-used and often better application.
  • Default member initializers make the implicit default constructor correct for free; the five members interact, which is Module 13.

One keyword has now appeared in this chapter and the two before it without explanation, on single-argument constructors specifically, and Chapter 12.2 §5 admitted it was there "because omitting it has a measured consequence". The consequence is the subject of the next chapter, and it is a conversion rather than an error: a constructor taking one argument teaches the compiler how to turn that argument's type into yours, and it will then do so silently, in places you did not intend and cannot see at the call site. Measured, a function expecting a buffer accepted a plain integer count, built a buffer of that capacity, and processed it — with no diagnostic of any kind.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.