#include <iostream>
#include <string>
#include <vector>
#include <initializer_list>
#include <utility>
using namespace std;
/*
============================================================
SHALLOW COPY VS DEEP COPY
============================================================
Shallow Copy:
- Copies the pointer address only.
- The original object and copied object share the same memory.
- Changing data through one object affects the other object.
- Dangerous with destructors because both objects may try to delete
the same memory.
Deep Copy:
- Allocates new memory for the copied object.
- Copies the actual values from the original object.
- Each object owns its own separate memory.
- Changing one object does not affect the other object.
*/
/*
============================================================
1) SHALLOW COPY EXAMPLE
============================================================
*/
class ShallowArray {
private:
int* data;
int size;
public:
/*
Parameterized Constructor
Syntax:
ClassName(parameters)
Purpose:
- Creates an object using given arguments.
- Here, it receives the array size and allocates memory.
*/
ShallowArray(int s) {
size = s;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = 0;
}
}
/*
Shallow Copy Constructor
Syntax:
ClassName(const ClassName& other)
Purpose:
- Creates a new object from another existing object.
Difference:
- This shallow copy constructor copies only the pointer address.
- It does NOT allocate a new array.
- Both objects point to the same memory.
*/
ShallowArray(const ShallowArray& other) {
size = other.size;
data = other.data;
}
void setValue(int index, int value) {
data[index] = value;
}
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
/*
Important Note:
We do not write a destructor here intentionally.
If we write:
delete[] data;
then both objects will try to delete the same memory because
shallow copy makes them share the same pointer.
This may cause double delete, which is undefined behavior.
*/
};
/*
============================================================
2) DEEP COPY EXAMPLE
============================================================
*/
class DeepArray {
private:
int* data;
int size;
public:
/*
Default Constructor
Syntax:
ClassName()
Purpose:
- Creates an object without receiving arguments.
- Here, it creates an empty array object.
*/
DeepArray() {
size = 0;
data = nullptr;
}
/*
Parameterized Constructor
Syntax:
ClassName(parameters)
Purpose:
- Creates an object using arguments.
- Here, it receives the array size and allocates memory.
*/
DeepArray(int s) {
size = s;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = 0;
}
}
/*
Deep Copy Constructor
Syntax:
ClassName(const ClassName& other)
Purpose:
- Creates a new object from another existing object.
Difference:
- This deep copy constructor allocates new memory.
- Then it copies the actual values.
- Each object has its own independent array.
*/
DeepArray(const DeepArray& other) {
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
/*
Copy Assignment Operator
Syntax:
ClassName& operator=(const ClassName& other)
Purpose:
- Used when assigning one existing object to another existing object.
Example:
DeepArray a(3);
DeepArray b(5);
b = a;
Difference from Copy Constructor:
- Copy constructor creates a new object.
- Copy assignment modifies an already existing object.
*/
DeepArray& operator=(const DeepArray& other) {
if (this == &other) {
return *this;
}
delete[] data;
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
return *this;
}
/*
Move Constructor
Syntax:
ClassName(ClassName&& other)
Purpose:
- Creates a new object by taking resources from another object.
- Usually used with temporary objects or std::move.
Difference from Copy Constructor:
- Copy constructor duplicates the resource.
- Move constructor transfers ownership of the resource.
- It is usually faster for dynamic memory or containers.
*/
DeepArray(DeepArray&& other) noexcept {
size = other.size;
data = other.data;
other.size = 0;
other.data = nullptr;
}
/*
Move Assignment Operator
Syntax:
ClassName& operator=(ClassName&& other)
Purpose:
- Transfers resources from one object to another existing object.
Difference from Move Constructor:
- Move constructor creates a new object.
- Move assignment modifies an already existing object.
*/
DeepArray& operator=(DeepArray&& other) noexcept {
if (this == &other) {
return *this;
}
delete[] data;
size = other.size;
data = other.data;
other.size = 0;
other.data = nullptr;
return *this;
}
/*
Destructor
Syntax:
~ClassName()
Purpose:
- Called automatically when the object is destroyed.
- Used here to free dynamic memory.
Important:
- Destructor is not a constructor.
- But it is important when the class owns dynamic memory.
*/
~DeepArray() {
delete[] data;
}
void setValue(int index, int value) {
data[index] = value;
}
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
/*
============================================================
3) CONSTRUCTORS EXAMPLES IN ONE CLASS
============================================================
*/
class Student {
private:
string name;
int age;
const int id;
int& mark;
vector<int> grades;
/*
This helper function returns a reference to a static integer.
Why do we need it?
- The class has a reference data member: int& mark.
- A reference must always refer to a valid variable.
- Some constructors do not receive a mark variable.
- So we use this dummy variable for those constructors.
*/
static int& dummyMark() {
static int value = 0;
return value;
}
public:
/*
Default Constructor
Syntax:
ClassName()
Example:
Student s;
Purpose:
- Creates an object without arguments.
Difference:
- It does not receive values from the caller.
- It uses default values written inside the constructor.
*/
Student()
: name("Unknown"),
age(0),
id(0),
mark(dummyMark()) {
cout << "Default constructor called\n";
}
/*
Parameterized Constructor
Syntax:
ClassName(type parameter1, type parameter2, ...)
Example:
Student s("Ali", 20, 1001, mark);
Purpose:
- Creates an object using values sent by the caller.
Difference:
- Unlike the default constructor, it receives arguments.
*/
Student(string n, int a, int studentId, int& m)
: name(n),
age(a),
id(studentId),
mark(m) {
cout << "Parameterized constructor called\n";
}
/*
Constructor With Default Arguments
Syntax:
ClassName(type parameter1, type parameter2 = defaultValue)
Examples:
Student s1("Mona");
Student s2("Omar", 22);
Purpose:
- Allows calling the same constructor with fewer arguments.
Difference:
- If the caller does not provide the second argument,
the default value is used.
*/
Student(string n, int a = 18)
: name(n),
age(a),
id(-1),
mark(dummyMark()) {
cout << "Constructor with default arguments called\n";
}
/*
initializer_list Constructor
Syntax:
ClassName(initializer_list<type> list)
Example:
Student s{90, 80, 70};
Purpose:
- Allows creating an object using a list of values inside braces.
Difference:
- This is useful when the number of input values may vary.
- It is different from member initializer list.
*/
Student(initializer_list<int> g)
: name("Grades Student"),
age(0),
id(-2),
mark(dummyMark()),
grades(g) {
cout << "initializer_list constructor called\n";
}
/*
Copy Constructor
Syntax:
ClassName(const ClassName& other)
Examples:
Student s2 = s1;
Student s3(s1);
Purpose:
- Creates a new object as a copy of an existing object.
Difference:
- It is called when a new object is being created from
another object of the same class.
*/
Student(const Student& other)
: name(other.name),
age(other.age),
id(other.id),
mark(other.mark),
grades(other.grades) {
cout << "Copy constructor called\n";
}
/*
Move Constructor
Syntax:
ClassName(ClassName&& other)
Example:
Student s2 = std::move(s1);
Purpose:
- Creates a new object by moving resources from another object.
Difference:
- It can avoid expensive copying.
- It is especially useful with strings, vectors, dynamic memory,
and temporary objects.
*/
Student(Student&& other) noexcept
: name(std::move(other.name)),
age(other.age),
id(other.id),
mark(other.mark),
grades(std::move(other.grades)) {
cout << "Move constructor called\n";
other.age = 0;
}
/*
Conversion Constructor
Syntax:
ClassName(singleParameter)
Example:
Student s = 25;
Purpose:
- Allows converting another type into this class type.
Difference:
- Because it has one parameter and is not explicit,
implicit conversion is allowed.
*/
Student(int a)
: name("Converted Student"),
age(a),
id(-3),
mark(dummyMark()) {
cout << "Conversion constructor called\n";
}
/*
Delegating Constructor
Syntax:
ClassName(parameters) : ClassName(otherArguments)
Example:
Student s('A');
Purpose:
- Allows one constructor to call another constructor
in the same class.
Difference:
- Reduces repeated initialization code.
*/
Student(char gradeLetter)
: Student(string("Grade ") + gradeLetter, 18) {
cout << "Delegating constructor called\n";
}
/*
Member Initializer List
Syntax:
Constructor(parameters) : member1(value1), member2(value2)
Purpose:
- Initializes members before entering the constructor body.
Difference:
- This is not a constructor type by itself.
- It is a constructor syntax.
- It is required for const members and reference members.
- It is usually better than assigning values inside the body.
*/
/*
Destructor
Syntax:
~ClassName()
Purpose:
- Called automatically when an object is destroyed.
Difference:
- It is not a constructor.
- It cleans up instead of creating the object.
*/
~Student() {
cout << "Destructor called for " << name << endl;
}
void print() const {
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
cout << "ID: " << id << endl;
cout << "Mark: " << mark << endl;
if (!grades.empty()) {
cout << "Grades: ";
for (int grade : grades) {
cout << grade << " ";
}
cout << endl;
}
cout << "-------------------------\n";
}
};
/*
============================================================
4) EXPLICIT CONSTRUCTOR EXAMPLE
============================================================
*/
class ExplicitExample {
private:
int value;
public:
/*
Explicit Constructor
Syntax:
explicit ClassName(type parameter)
Examples:
ExplicitExample e1(10);
ExplicitExample e2{20};
Purpose:
- Prevents implicit conversion.
Difference from Conversion Constructor:
- Conversion constructor allows:
ExplicitExample e = 10;
- Explicit constructor prevents that.
*/
explicit ExplicitExample(int v) {
value = v;
cout << "Explicit constructor called\n";
}
void print() const {
cout << "Value: " << value << endl;
}
};
/*
============================================================
5) PRIVATE CONSTRUCTOR EXAMPLE
============================================================
*/
class Logger {
private:
/*
Private Constructor
Syntax:
private:
ClassName()
Purpose:
- Prevents creating objects directly from outside the class.
Difference:
- Normal public constructors can be called from outside.
- Private constructors can only be called from inside the class.
*/
Logger() {
cout << "Private constructor called\n";
}
public:
/*
Static function used to access the single Logger object.
This is a simple Singleton-style example.
*/
static Logger& getInstance() {
static Logger instance;
return instance;
}
void log(const string& message) {
cout << "LOG: " << message << endl;
}
};
/*
============================================================
6) DEFAULTED AND DELETED CONSTRUCTORS EXAMPLE
============================================================
*/
class ConstructorControl {
private:
int value = 0;
public:
/*
Defaulted Constructor
Syntax:
ClassName() = default;
Purpose:
- Asks the compiler to generate the default constructor.
Difference:
- Useful when you want compiler-generated behavior explicitly.
*/
ConstructorControl() = default;
/*
Parameterized Constructor
*/
ConstructorControl(int v) {
value = v;
cout << "ConstructorControl parameterized constructor called\n";
}
/*
Deleted Copy Constructor
Syntax:
ClassName(const ClassName& other) = delete;
Purpose:
- Prevents copying objects of this class.
Difference:
- If someone tries to copy, the code will not compile.
*/
ConstructorControl(const ConstructorControl& other) = delete;
void print() const {
cout << "Value: " << value << endl;
}
};
/*
============================================================
7) CONSTEXPR CONSTRUCTOR EXAMPLE
============================================================
*/
class Point {
private:
int x;
int y;
public:
/*
constexpr Constructor
Syntax:
constexpr ClassName(parameters)
Purpose:
- Allows creating objects that can be evaluated at compile time.
Difference:
- Useful for constant objects and compile-time calculations.
*/
constexpr Point(int xValue, int yValue)
: x(xValue),
y(yValue) {
}
constexpr int getX() const {
return x;
}
constexpr int getY() const {
return y;
}
};
/*
============================================================
MAIN FUNCTION
============================================================
*/
int main() {
cout << "================ SHALLOW COPY ================\n";
ShallowArray shallow1(3);
shallow1.setValue(0, 10);
shallow1.setValue(1, 20);
shallow1.setValue(2, 30);
/*
This calls the shallow copy constructor.
Both shallow1 and shallow2 will share the same array.
*/
ShallowArray shallow2 = shallow1;
cout << "shallow1 before change: ";
shallow1.print();
cout << "shallow2 before change: ";
shallow2.print();
shallow2.setValue(0, 999);
cout << "shallow1 after changing shallow2: ";
shallow1.print();
cout << "shallow2 after changing shallow2: ";
shallow2.print();
cout << "\n";
cout << "================ DEEP COPY ================\n";
DeepArray deep1(3);
deep1.setValue(0, 10);
deep1.setValue(1, 20);
deep1.setValue(2, 30);
/*
This calls the deep copy constructor.
deep1 and deep2 will have separate arrays.
*/
DeepArray deep2 = deep1;
cout << "deep1 before change: ";
deep1.print();
cout << "deep2 before change: ";
deep2.print();
deep2.setValue(0, 999);
cout << "deep1 after changing deep2: ";
deep1.print();
cout << "deep2 after changing deep2: ";
deep2.print();
cout << "\n";
cout << "================ COPY ASSIGNMENT ================\n";
DeepArray deep3(2);
deep3.setValue(0, 7);
deep3.setValue(1, 8);
cout << "deep3 before assignment: ";
deep3.print();
/*
This calls the copy assignment operator, not the copy constructor.
deep3 already exists, so assignment is used.
*/
deep3 = deep2;
cout << "deep3 after assignment from deep2: ";
deep3.print();
cout << "\n";
cout << "================ MOVE CONSTRUCTOR ================\n";
/*
This calls the move constructor.
deep3 gives its internal array to deep4.
*/
DeepArray deep4 = std::move(deep3);
cout << "deep4 after move: ";
deep4.print();
cout << "\n";
cout << "================ MOVE ASSIGNMENT ================\n";
DeepArray deep5(1);
deep5.setValue(0, 555);
/*
This calls the move assignment operator.
deep5 already exists, so move assignment is used.
*/
deep5 = std::move(deep4);
cout << "deep5 after move assignment: ";
deep5.print();
cout << "\n";
cout << "================ STUDENT CONSTRUCTORS ================\n";
int mark = 95;
/*
Default constructor
*/
Student s1;
s1.print();
/*
Parameterized constructor
*/
Student s2("Ali", 20, 1001, mark);
s2.print();
/*
Constructor with default arguments
The age is not passed, so it uses the default value 18.
*/
Student s3("Mona");
s3.print();
/*
Constructor with default arguments
The age is passed explicitly.
*/
Student s4("Omar", 22);
s4.print();
/*
initializer_list constructor
*/
Student s5{90, 80, 70};
s5.print();
/*
Copy constructor
*/
Student s6 = s2;
s6.print();
/*
Move constructor
*/
Student s7 = std::move(s3);
s7.print();
/*
Conversion constructor
The integer 25 is converted into a Student object.
*/
Student s8 = 25;
s8.print();
/*
Delegating constructor
The char constructor calls another constructor.
*/
Student s9('A');
s9.print();
cout << "\n";
cout << "================ EXPLICIT CONSTRUCTOR ================\n";
/*
Direct initialization is allowed.
*/
ExplicitExample e1(10);
e1.print();
/*
Brace initialization is allowed.
*/
ExplicitExample e2{20};
e2.print();
/*
Copy initialization is not allowed because the constructor is explicit.
This line would cause a compilation error:
ExplicitExample e3 = 30;
*/
cout << "\n";
cout << "================ PRIVATE CONSTRUCTOR ================\n";
/*
Direct object creation is not allowed because the constructor is private.
This line would cause a compilation error:
Logger logger;
*/
Logger& logger = Logger::getInstance();
logger.log("This object was created using a private constructor.");
cout << "\n";
cout << "================ DEFAULTED AND DELETED CONSTRUCTORS ================\n";
/*
Defaulted constructor
*/
ConstructorControl c1;
c1.print();
/*
Parameterized constructor
*/
ConstructorControl c2(50);
c2.print();
/*
Copying is not allowed because the copy constructor is deleted.
This line would cause a compilation error:
ConstructorControl c3 = c2;
*/
cout << "\n";
cout << "================ CONSTEXPR CONSTRUCTOR ================\n";
/*
This object can be created at compile time.
*/
constexpr Point p(3, 4);
cout << "Point x = " << p.getX() << endl;
cout << "Point y = " << p.getY() << endl;
cout << "\n";
return 0;
}