Advanced Classes
Advanced Classes¶
1. Operator Overloading¶
You can define how operators work with your classes.
Basic Syntax¶
return_type operator symbol(parameters) {
// implementation
}
Arithmetic Operator Overloading¶
#include <iostream>
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// + operator (member function)
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
// - operator
Vector2D operator-(const Vector2D& other) const {
return Vector2D(x - other.x, y - other.y);
}
// * operator (scalar multiplication)
Vector2D operator*(double scalar) const {
return Vector2D(x * scalar, y * scalar);
}
void print() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};
int main() {
Vector2D v1(3, 4);
Vector2D v2(1, 2);
Vector2D v3 = v1 + v2; // operator+ called
v3.print(); // (4, 6)
Vector2D v4 = v1 - v2;
v4.print(); // (2, 2)
Vector2D v5 = v1 * 2;
v5.print(); // (6, 8)
return 0;
}
Comparison Operator Overloading¶
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
Person(std::string n, int a) : name(n), age(a) {}
// == operator
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
// != operator
bool operator!=(const Person& other) const {
return !(*this == other);
}
// < operator (by age)
bool operator<(const Person& other) const {
return age < other.age;
}
};
int main() {
Person p1("Alice", 25);
Person p2("Alice", 25);
Person p3("Bob", 30);
std::cout << std::boolalpha;
std::cout << (p1 == p2) << std::endl; // true
std::cout << (p1 != p3) << std::endl; // true
std::cout << (p1 < p3) << std::endl; // true
return 0;
}
Compound Assignment Operators¶
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// += operator
Vector2D& operator+=(const Vector2D& other) {
x += other.x;
y += other.y;
return *this;
}
// -= operator
Vector2D& operator-=(const Vector2D& other) {
x -= other.x;
y -= other.y;
return *this;
}
};
Increment/Decrement Operators¶
#include <iostream>
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// Prefix increment (++c)
Counter& operator++() {
++value;
return *this;
}
// Postfix increment (c++)
Counter operator++(int) { // int is a dummy to distinguish
Counter temp = *this;
++value;
return temp;
}
int getValue() const { return value; }
};
int main() {
Counter c(5);
std::cout << (++c).getValue() << std::endl; // 6
std::cout << (c++).getValue() << std::endl; // 6
std::cout << c.getValue() << std::endl; // 7
return 0;
}
Stream Operators (friend)¶
#include <iostream>
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// << operator (friend function)
friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
// >> operator
friend std::istream& operator>>(std::istream& is, Vector2D& v) {
is >> v.x >> v.y;
return is;
}
};
int main() {
Vector2D v(3, 4);
std::cout << "Vector: " << v << std::endl; // Vector: (3, 4)
Vector2D v2;
std::cout << "Enter x y: ";
std::cin >> v2;
std::cout << "Input: " << v2 << std::endl;
return 0;
}
Function Call Operator ()¶
#include <iostream>
class Adder {
private:
int base;
public:
Adder(int b) : base(b) {}
// () operator - can be called like a function
int operator()(int x) const {
return base + x;
}
int operator()(int x, int y) const {
return base + x + y;
}
};
int main() {
Adder add10(10);
std::cout << add10(5) << std::endl; // 15
std::cout << add10(5, 3) << std::endl; // 18
return 0;
}
Subscript Operator []¶
#include <iostream>
#include <stdexcept>
class SafeArray {
private:
int* data;
int size;
public:
SafeArray(int s) : size(s) {
data = new int[size]();
}
~SafeArray() {
delete[] data;
}
// [] operator (read/write)
int& operator[](int index) {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
// const version (read-only)
const int& operator[](int index) const {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
};
int main() {
SafeArray arr(5);
arr[0] = 10;
arr[1] = 20;
std::cout << arr[0] << std::endl; // 10
std::cout << arr[1] << std::endl; // 20
// arr[10] = 100; // Exception thrown!
return 0;
}
2. Copy Constructor¶
Called when an object is copied.
Basic Copy¶
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
Person(std::string n, int a) : name(n), age(a) {
std::cout << "Regular constructor" << std::endl;
}
// Copy constructor
Person(const Person& other) : name(other.name), age(other.age) {
std::cout << "Copy constructor" << std::endl;
}
};
int main() {
Person p1("Alice", 25); // Regular constructor
Person p2(p1); // Copy constructor
Person p3 = p1; // Copy constructor
return 0;
}
Shallow Copy vs Deep Copy¶
#include <iostream>
#include <cstring>
class String {
private:
char* data;
int length;
public:
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// Deep copy constructor
String(const String& other) {
length = other.length;
data = new char[length + 1]; // Allocate new memory
strcpy(data, other.data); // Copy contents
std::cout << "Deep copy" << std::endl;
}
~String() {
delete[] data;
}
void print() const {
std::cout << data << std::endl;
}
};
int main() {
String s1("Hello");
String s2 = s1; // Deep copy
s1.print(); // Hello
s2.print(); // Hello
return 0;
}
3. Copy Assignment Operator¶
Called when assigning to an existing object.
#include <iostream>
#include <cstring>
class String {
private:
char* data;
int length;
public:
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// Copy assignment operator
String& operator=(const String& other) {
if (this != &other) { // Self-assignment check
delete[] data; // Free existing memory
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this;
}
~String() {
delete[] data;
}
void print() const {
std::cout << data << std::endl;
}
};
int main() {
String s1("Hello");
String s2("World");
s2 = s1; // Copy assignment operator
s1.print(); // Hello
s2.print(); // Hello
return 0;
}
4. Move Semantics (C++11)¶
"Move" resources from temporary objects to avoid unnecessary copies.
Move Constructor¶
#include <iostream>
#include <cstring>
#include <utility> // std::move
class String {
private:
char* data;
int length;
public:
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
std::cout << "Regular constructor" << std::endl;
}
// Copy constructor
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
std::cout << "Copy constructor" << std::endl;
}
// Move constructor
String(String&& other) noexcept {
data = other.data; // Just copy pointer
length = other.length;
other.data = nullptr; // Invalidate original
other.length = 0;
std::cout << "Move constructor" << std::endl;
}
~String() {
delete[] data;
}
void print() const {
if (data) std::cout << data << std::endl;
else std::cout << "(empty)" << std::endl;
}
};
int main() {
String s1("Hello"); // Regular constructor
String s2 = s1; // Copy constructor
String s3 = std::move(s1); // Move constructor
// s1 is now empty
s1.print(); // (empty) - moved
s2.print(); // Hello
s3.print(); // Hello
return 0;
}
Move Assignment Operator¶
// Move assignment operator
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data; // Free existing memory
data = other.data; // Move pointer
length = other.length;
other.data = nullptr; // Invalidate original
other.length = 0;
}
std::cout << "Move assignment" << std::endl;
return *this;
}
Rule of Five¶
Classes that manage resources should define all 5:
- Destructor
- Copy constructor
- Copy assignment operator
- Move constructor
- Move assignment operator
class Resource {
public:
Resource(); // Constructor
~Resource(); // 1. Destructor
Resource(const Resource& other); // 2. Copy constructor
Resource& operator=(const Resource& other); // 3. Copy assignment
Resource(Resource&& other) noexcept; // 4. Move constructor
Resource& operator=(Resource&& other) noexcept; // 5. Move assignment
};
5. static Members¶
Members shared by all objects of a class.
static Member Variables¶
#include <iostream>
class Counter {
private:
static int count; // Declaration
public:
Counter() {
count++;
}
~Counter() {
count--;
}
static int getCount() { // static member function
return count;
}
};
// Definition (outside class)
int Counter::count = 0;
int main() {
std::cout << "Count: " << Counter::getCount() << std::endl; // 0
Counter c1;
Counter c2;
std::cout << "Count: " << Counter::getCount() << std::endl; // 2
{
Counter c3;
std::cout << "Count: " << Counter::getCount() << std::endl; // 3
}
std::cout << "Count: " << Counter::getCount() << std::endl; // 2
return 0;
}
static Member Functions¶
#include <iostream>
class Math {
public:
static int add(int a, int b) {
return a + b;
}
static int multiply(int a, int b) {
return a * b;
}
static const double PI;
};
const double Math::PI = 3.14159;
int main() {
// Call without object
std::cout << Math::add(3, 5) << std::endl; // 8
std::cout << Math::multiply(3, 5) << std::endl; // 15
std::cout << Math::PI << std::endl; // 3.14159
return 0;
}
6. friend¶
External functions or classes that can access private members.
friend Function¶
#include <iostream>
class Box {
private:
double width;
public:
Box(double w) : width(w) {}
// friend function declaration
friend void printWidth(const Box& b);
friend double addWidths(const Box& a, const Box& b);
};
// friend function definition
void printWidth(const Box& b) {
std::cout << "Width: " << b.width << std::endl; // Can access private
}
double addWidths(const Box& a, const Box& b) {
return a.width + b.width;
}
int main() {
Box b1(10), b2(20);
printWidth(b1); // Width: 10
std::cout << "Sum: " << addWidths(b1, b2) << std::endl; // Sum: 30
return 0;
}
friend Class¶
#include <iostream>
class Engine {
private:
int horsepower;
public:
Engine(int hp) : horsepower(hp) {}
friend class Car; // Car can access Engine's private members
};
class Car {
private:
Engine engine;
public:
Car(int hp) : engine(hp) {}
void showHorsepower() const {
std::cout << "Horsepower: " << engine.horsepower << std::endl;
}
};
int main() {
Car car(300);
car.showHorsepower(); // Horsepower: 300
return 0;
}
7. explicit¶
Prevents implicit conversions.
#include <iostream>
class Fraction {
private:
int numerator;
int denominator;
public:
// Without explicit, Fraction f = 5; would work
explicit Fraction(int n, int d = 1) : numerator(n), denominator(d) {}
void print() const {
std::cout << numerator << "/" << denominator << std::endl;
}
};
void printFraction(const Fraction& f) {
f.print();
}
int main() {
Fraction f1(3, 4);
f1.print(); // 3/4
Fraction f2(5); // Explicit call OK
f2.print(); // 5/1
// Fraction f3 = 5; // Error! explicit
// printFraction(10); // Error! No implicit conversion
printFraction(Fraction(10)); // OK: Explicit conversion
return 0;
}
8. Practice Example: Complete String Class¶
#include <iostream>
#include <cstring>
#include <utility>
class String {
private:
char* data;
size_t length;
public:
// Default constructor
String() : data(nullptr), length(0) {
data = new char[1];
data[0] = '\0';
}
// String constructor
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// Copy constructor
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// Move constructor
String(String&& other) noexcept
: data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
}
// Destructor
~String() {
delete[] data;
}
// Copy assignment
String& operator=(const String& other) {
if (this != &other) {
delete[] data;
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this;
}
// Move assignment
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
}
return *this;
}
// + operator
String operator+(const String& other) const {
char* newData = new char[length + other.length + 1];
strcpy(newData, data);
strcat(newData, other.data);
String result(newData);
delete[] newData;
return result;
}
// == operator
bool operator==(const String& other) const {
return strcmp(data, other.data) == 0;
}
// [] operator
char& operator[](size_t index) {
return data[index];
}
const char& operator[](size_t index) const {
return data[index];
}
// << operator
friend std::ostream& operator<<(std::ostream& os, const String& s) {
return os << s.data;
}
size_t size() const { return length; }
const char* c_str() const { return data; }
};
int main() {
String s1("Hello");
String s2(" World");
String s3 = s1 + s2;
std::cout << s3 << std::endl; // Hello World
std::cout << "Length: " << s3.size() << std::endl; // 11
return 0;
}
9. Summary¶
| Concept | Description |
|---|---|
| Operator overloading | Define operators for classes |
| Copy constructor | T(const T&) |
| Copy assignment | T& operator=(const T&) |
| Move constructor | T(T&&) |
| Move assignment | T& operator=(T&&) |
static |
Shared class member |
friend |
Allow private access |
explicit |
Prevent implicit conversion |
Next Step¶
Let's learn about inheritance and polymorphism in 09_Inheritance_and_Polymorphism.md!