Core Concepts of Constructors, Destructors, and Operator Overloading in C++ Classes
Default Member Functions
Default member functions are those implicitly generated by the compiler when a class does not explicitly define them. For an empty class, the compiler creates six default members, with the first four being essential: constructor, destructor, copy constructor, and assignment operator. Two additional defaults—move constructor and move assignment—are added from C++11 onward.
Two perspectives help in mastering these:
- Understand the compiler-generated behavior for unimplemented cases.
- Know how to implement custom versions when default behavior is inadequate.
Constructor
Constructors initialize objects at instantiation rather than allocate memory. They replace manual Init methods used in older designs. Characteristics:
- Name matches the class name.
- No return type, not even
void. - Invoked automatically during object creation.
- Can be overloaded.
- If no constructor is defined, a compiler-generated parameterless one is created; defining any constructor suppresses this automatic generation.
- Parameterless, default-parameter, and compiler-generated constructors are all considered default constructors. Only one may exist to avoid ambiguity.
- Compiler-generated constructors leave built-in-type members uninitialized (behavior compiler-dependent), but call the default constructor of each member of class type. If such a member lacks a default constructor, initialization fails unless an initializer list is used.
#include <iostream>
#include <cstdlib>
using namespace std;
typedef int DataType;
class Buffer {
public:
Buffer(size_t sz = 8) {
ptr = static_cast<DataType*>(malloc(sizeof(DataType) * sz));
if (!ptr) {
perror("malloc failed");
return;
}
cap = sz;
len = 0;
}
private:
DataType* ptr;
size_t cap;
size_t len;
};
class QueueFromBuffers {
Buffer enqueueBuf;
Buffer dequeueBuf;
};
int main() {
QueueFromBuffers qb; // compiler calls Buffer() for both members
}
Example with explicit constructors:
#include <iostream>
using namespace std;
class Calendar {
public:
Calendar() : yr(1), mo(1), dy(1) {
cout << "no-arg ctor\n";
}
Calendar(int y, int m, int d) : yr(y), mo(m), dy(d) {
cout << "arg ctor\n";
}
void display() const {
cout << yr << "/" << mo << "/" << dy << "\n";
}
private:
int yr, mo, dy;
};
int main() {
Calendar c1; // uses no-arg ctor
Calendar c2(2025, 1, 1); // uses arg ctor
}
Destructor
Destructors clean up resources when an object’s lifetime ends; they do not destroy the storage itself (stack cleanup is automatic). They correspond to manual Destroy patterns. Characteristics:
- Name is
~ClassName. - No parameters, no return type.
- At most one destructor per class; compiler supplies one if absent.
- Called automatically at end of scope.
- Compiler-generated version ignores built-in types but invokes destructors of class-type members.
- Explicit destructor still ensures class-type members are properly destroyed.
Copy Constructor
A copy constructor initializes a new object using an existing object of the same class. Its first parameter is a reference to the class type; extra parameters have defaults.
Characteristics:
- It is a special constructor, overload of the normal constructor.
- First argument must be a reference; passing by value causes infinite recursion.
- Copying a class-type object by value (parameter or return) triggers the copy constructor.
- Absent user definition, compiler generates one performing bitwise (shallow) copy for built-ins and invoking member copy constructors for class types.
- For classes managing resources (e.g., dynamic memory), shallow copy is unsafe; deep copy must be implemented manually.
- If a class defines a destructor releasing resources, it likely needs a custom copy constructor.
- Return-by-value creates a temporary via copy construction; returning a reference avoids this if lifetime permits.
Assignment Operator Overloading
Assignment assigns values between two existing objects (distinct from copy construction which creates a new object).
Operator Overloading Basics
Operators can be redefined for class types. An overloaded operator is a function named operatorX where X is the symbol. Rules:
- Number of parameters equals number of operands (one for unary, two for binary).
- As a member functon, the left operand is
this; parameter count is one less. - Precedence and associativity match the built-in version.
- Cannot create new operators; five operators (
::,.*,sizeof,?:,.) cannot be overloaded. - At least one parameter must be class type; cannot alter meaning for built-ins.
Example of pointer-to-member usage:
#include <iostream>
using namespace std;
class Demo {
public:
void show() { cout << "Demo::show()\n"; }
};
typedef void(Demo::*MemFunc)();
int main() {
Demo obj;
MemFunc f = &Demo::show;
(obj.*f)();
}
Assignment Operator Implementation
Rules:
- Must be a member function.
- Prefer
const ClassName¶meter to avoid unnecessary copying. - Return
*thisby reference for chaining. - Compiler-generated version copies built-ins bitwise and calls assignment operators of class-type members.
- Implement manually for resource-managing classes.
- Presence of a user-defined destructor often implies need for custom assignment.
Calendar Class Example
Header:
#pragma once
#include <iostream>
#include <cassert>
class Calendar {
friend std::ostream& operator<<(std::ostream&, const Calendar&);
friend std::istream& operator>>(std::istream&, Calendar&);
public:
Calendar(int y, int m, int d);
Calendar(const Calendar& src)
: yr(src.yr), mo(src.mo), dy(src.dy) {}
int daysInMon(int y, int m) const;
void output() const;
bool isValid() const;
bool lt(const Calendar& other) const;
bool lte(const Calendar& other) const;
bool gt(const Calendar& other) const;
bool gte(const Calendar& other) const;
bool eq(const Calendar& other) const;
bool neq(const Calendar& other) const;
Calendar& incDays(int n);
Calendar plusDays(int n) const;
Calendar& decDays(int n);
Calendar minusDays(int n) const;
int diff(const Calendar& other) const;
Calendar& preInc();
Calendar postInc();
Calendar& preDec();
Calendar postDec();
private:
int yr, mo, dy;
};
Key implementation ideas (excerpt):
isValid()checks range constraints.- Comparison operators use lexicographic year/month/day evaluation.
- Arithmetic operators adjust date fields hendling month/year roll-over.
- Increment/decrement operators distinguish pre/post via dummy
intparameter for postfix. - Binary
-computes day difference via iterative approach. - Stream operators handle formatted I/O with validation.
Efficiency note: arithmetic operators like minusDays delegate to compound assignment (decDays) to minimize coppy constructions.
Address-of Operator Overloading
Const Member Functions
Adding const after the parameter list makes a member function read-only by binding this to const ClassType* const. This prevents modification of member data.
Overloading &
Two forms: one for ordinary objects, one for const objects. Usually compiler-generated versions suffice. Custom versions can restrict address exposure.
class Calendar {
public:
Calendar* addr() { return this; } // non-const
const Calendar* addr() const { return this; } // const
private:
int yr, mo, dy;
};
The appropriate version is selected based on object’s const-ness.