Compiler-Generated Special Member Functions in C++
You define an empty class in C++:
```cpp class A {}; ```
No constructors, no destructor, no operators -- nothing. What member functions will the compiler generate for you automatically? What does each one do, and when does the compiler stop generating them?
Hints
- Count the operations: construction, copying, moving, destruction. For each, there may be more than one function.
- Remember that C++11 added move semantics -- the list grew from four to six in modern C++.
- The 'Rule of Five' describes when you should define these yourself: if you define one (because you manage a resource), you probably need all five.
Worked Solution
How to Think About It: This is a standard C++ interview question that tests whether you understand the "Rule of Five" (and its history). The compiler's job is to make basic operations work on your class even if you don't write anything -- but it can only do that safely when there's nothing non-trivial to manage. The moment you take ownership of a resource (say, a raw pointer), the compiler's auto-generated versions become wrong, and you have to write your own.
The Six Auto-Generated Functions
For class A {};, the compiler generates all six special member functions:
- Default constructor
A()-- constructs an object with no arguments. Does nothing for an empty class.
- Copy constructor
A(const A&)-- constructs a new object as a copy. Performs member-wise copy of all data members.
- Copy assignment operator
A& operator=(const A&)-- assigns from another existing object. Performs member-wise copy assignment.
- Move constructor
A(A&&)*(C++11)* -- constructs from an rvalue (typically a temporary). Performs member-wise move.
- Move assignment operator
A& operator=(A&&)*(C++11)* -- move-assigns from an rvalue. Performs member-wise move assignment.
- Destructor
~A()-- cleans up when the object goes out of scope. Does nothing for an empty class.
When Does the Compiler Stop Generating Them?
C++11 introduced suppression rules to prevent the compiler from silently generating incorrect code:
- If you declare a destructor, copy constructor, or copy assignment, the compiler does *not* implicitly generate move operations.
- If you declare a move constructor or move assignment, the compiler does *not* implicitly generate the other move operation, and suppresses implicit copy operations.
- The default constructor is suppressed if you declare *any* constructor.
Practical implication: if your class manages a resource (owns a pointer, holds a file handle), you need to think about all five copy/move/destroy operations. That's the Rule of Five: if you need to explicitly define any one of the destructor, copy constructor, copy assignment, move constructor, or move assignment, you almost certainly need to define all five.
Answer: Six functions are generated: default constructor, copy constructor, copy assignment, move constructor, move assignment, and destructor. The set actually generated for any class is the subset of these six left after applying the suppression rules in order. Note the legacy quirk: declaring only a destructor still implicitly generates the copy operations (this is deprecated) but suppresses both move operations; declaring any move operation suppresses both copy operations and the other move operation; and declaring any constructor suppresses the default constructor. C++11's suppression rules mean declaring any one of the five copy/move/destroy operations may prevent the compiler from generating the others.
Intuition
The compiler-generated functions exist so that simple structs and value types "just work" without boilerplate. For int x = 5; int y = x;, copy semantics are obvious. The compiler extends this to user-defined types by doing member-wise copies. This is correct as long as your members are themselves value types or RAII wrappers.
The Rule of Five is where things get interesting. If your class holds a raw pointer to heap memory, the compiler-generated copy constructor will copy the pointer -- both objects now own the same memory, and you'll get a double free when they both destruct. That's a bug the compiler can't detect. The rule reminds you: as soon as you write a destructor that does anything non-trivial, you're taking ownership of a resource, and you must handle all five resource-management operations explicitly. In modern C++, the preferred solution is often to use std::unique_ptr or std::shared_ptr and let the zero-overhead wrappers handle it -- then the compiler-generated defaults are correct again.