C++ Classes and Objects
Introduction to Classes and Objects
In C++, a class is a user-defined type that groups data and the functions that operate on that data. An object is an instance of a class.
Classes are the foundation of Object-Oriented Programming (OOP) in C++ and provide:
- Data encapsulation (bundling data with methods)
- Access control (public/private/protected members)
- Code reusability through inheritance
- Polymorphism (multiple forms for functions/operators)
// Simple class definition
#include <iostream>
#include <string>
using namespace std;
class Book {
// Data members (attributes) - private by default
string title;
string author;
int pages;
public:
// Member function (method)
void displayInfo() {
cout << title << " by " << author << ", " << pages << " pages";
}
};
Class vs Structure
// Class (members are private by default)
class CircleClass {
double radius; // private by default
};
// Struct (members are public by default)
struct CircleStruct {
double radius; // public by default
};
Feature | Class | Structure |
---|---|---|
Default member access | private | public |
Default base-class access (when inheriting) | private | public |
Typical use | Types with behavior and invariants | Plain data aggregates |
Methods | Supported | Supported |
Inheritance support | Supported | Supported |
Creating Objects
Objects can be created in several ways:
- On the stack (automatic storage)
- On the heap (dynamic allocation; prefer smart pointers)
- As members of other classes
- As static or global objects
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class Book {
public:
Book() = default;
Book(string t, string a, int p)
: title(move(t)), author(move(a)), pages(p) {}
void print() const {
cout << '"' << title << '"' << " by " << author
<< ", " << pages << " pages\n";
}
private:
string title{"Untitled"};
string author{"Unknown"};
int pages{0};
};
int main() {
Book book1; // Default constructor
Book book2("C++ Primer", "Lippman", 976);
auto book3 = make_unique<Book>();
auto book4 = make_unique<Book>("Effective C++", "Meyers", 320);
Book library[3];
book2.print();
}
Accessing Members
Use the dot operator (.) for objects and the arrow operator (->) for pointers.
Access depends on access specifiers:
- public: Accessible from anywhere
- private: Accessible only inside the class (default for classes)
- protected: Accessible within the class and derived classes
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
string name;
void display() {
cout << name << " - " << id;
}
private:
int id{0};
};
int main() {
Student s;
s.name = "Alice"; // OK
// s.id = 123; // Error: private
s.display();
Student* ptr = new Student();
ptr->name = "Bob";
delete ptr;
}
Member Functions
Member functions can be defined:
- Inside the class (implicitly inline)
- Outside the class using the scope resolution operator (::)
Special functions include constructors, destructors, copy/move operations, operator overloads, and conversions.
class Rectangle {
int width, height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int area() const;
void scale(int factor) {
width *= factor;
height *= factor;
}
};
int Rectangle::area() const {
return width * height;
}
Constructors and Destructors
Constructors initialize objects, and destructors clean up resources.
Types of constructors:
- Default
- Parameterized
- Copy
- Move (C++11+)
- Delegating (C++11+)
#include <iostream>
#include <string>
using namespace std;
class Person {
string name;
int age;
public:
Person() : name("Unknown"), age(0) {}
Person(string n, int a) : name(move(n)), age(a) {}
Person(const Person& other) : name(other.name), age(other.age) {}
~Person() {
cout << name << " destroyed" << endl;
}
Person(string n) : Person(move(n), 0) {}
};
The 'this' Pointer
Each non-static member function has a hidden this pointer referring to the object.
Common uses:
- Resolve name conflicts
- Return *this for method chaining
- Pass the current object to other functions
#include <iostream>
using namespace std;
class Counter {
int count;
public:
Counter() : count(0) {}
Counter& setCount(int count) { this->count = count; return *this; }
Counter& increment() { ++count; return *this; }
int getCount() const { return count; }
};
int main() {
Counter c;
c.setCount(5).increment().increment();
cout << c.getCount();
}
Static Members
Static members belong to the class, not an instance.
- Only one copy exists regardless of objects
- Can be accessed without creating an object
- Since C++17, inline static data members can be defined inside the class
#include <iostream>
using namespace std;
class Employee {
inline static int count = 0;
int id;
public:
Employee() : id(++count) {}
static int getTotal() { return count; }
};
int main() {
cout << Employee::getTotal() << "\n";
Employee e1, e2;
cout << Employee::getTotal() << "\n";
}
Const Objects and Methods
const objects cannot be modified. const member functions guarantee not to modify the object state.
- Const objects can only call const member functions
- Const methods cannot modify members (except mutable
ones)
- Improves API clarity but does not ensure thread safety
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
double balance;
mutable int accessCount;
public:
BankAccount(double b) : balance(b), accessCount(0) {}
double getBalance() const {
++accessCount; // OK
return balance;
}
void deposit(double amount) { balance += amount; }
};
int main() {
const BankAccount savings(1000);
double b = savings.getBalance();
// savings.deposit(100); // Error
}
Friend Functions and Classes
A friend function or class can access private and protected members of another class.
Use cases:
- Operator overloading (e.g., I/O operators)
- Close cooperation between two types
- Utility functions needing internal access
#include <iostream>
using namespace std;
class Box {
double width;
public:
explicit Box(double w) : width(w) {}
friend void printWidth(const Box& b);
friend class BoxPrinter;
};
void printWidth(const Box& b) { cout << b.width << endl; }
class BoxPrinter {
public:
void print(const Box& b) { cout << b.width << endl; }
};
Practical Example: Library System
Example demonstrating encapsulation, methods, and object interaction:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Book {
string title;
string author;
int year;
bool checkedOut;
public:
Book(string t, string a, int y)
: title(move(t)), author(move(a)), year(y), checkedOut(false) {}
void checkOut() {
if (!checkedOut) { checkedOut = true; cout << title << " checked out" << endl; }
else { cout << title << " already checked out" << endl; }
}
void returnBook() {
if (checkedOut) { checkedOut = false; cout << title << " returned" << endl; }
else { cout << title << " wasn't checked out" << endl; }
}
void display() const {
cout << '"' << title << '"' << " by " << author << " (" << year << ") - "
<< (checkedOut ? "Checked Out" : "Available") << endl;
}
};
class Library {
vector<Book> books;
public:
void addBook(const Book& b) { books.push_back(b); }
void listBooks() const {
cout << "\nLibrary Catalog:" << endl;
for (const auto& b : books) b.display();
}
void checkOutBook(size_t i) {
if (i < books.size()) books[i].checkOut();
else cout << "Invalid index" << endl;
}
void returnBook(size_t i) {
if (i < books.size()) books[i].returnBook();
else cout << "Invalid index" << endl;
}
};
int main() {
Library lib;
lib.addBook(Book("1984", "Orwell", 1949));
lib.addBook(Book("The Great Gatsby", "Fitzgerald", 1925));
lib.listBooks();
lib.checkOutBook(0);
lib.checkOutBook(0);
lib.returnBook(0);
}
Library Catalog: "1984" by Orwell (1949) - Available "The Great Gatsby" by Fitzgerald (1925) - Available 1984 checked out 1984 already checked out 1984 returned
Best Practices
Practice | Description | Example |
---|---|---|
RAII | Manage resources with object lifetime | Use constructors/destructors or smart pointers |
Minimal Interface | Expose only necessary operations | Keep members private; use accessors |
Single Responsibility | One cohesive purpose per class | Separate logic from I/O or storage |
Const Correctness | Use const where methods don’t change state | double getValue() const; |
Prefer Composition | Favor composition over inheritance when possible | class Car { Engine engine; } |