Appearance
Classes and Structs
Video: Object Oriented Programming (OOP) in C++ Course
Object-Oriented Programming (OOP) is a fundamental paradigm in C++ that allows you to organize code into logical units called classes and structs. These user-defined types encapsulate data and behavior, providing a powerful way to model real-world entities and create maintainable, reusable code.
What are Classes and Structs?
Classes and structs are user-defined types that group related data and functions together. They serve as blueprints for creating objects - instances of the class or struct that contain actual data values.
Key Concepts:
- Encapsulation: Bundling data and methods that operate on that data
- Data Abstraction: Hiding complex implementation details behind a simple interface
- Access Control: Managing how class members can be accessed and modified
- Object Creation: Instantiating classes to create objects with specific data
Classes vs Structs: The Key Difference
The main difference between classes and structs in C++ is the default access level:
cpp
// Class: members are private by default
class BankAccount {
double balance; // private by default
std::string owner; // private by default
public:
void deposit(double amount); // explicitly public
double getBalance() const; // explicitly public
};
// Struct: members are public by default
struct Point {
int x; // public by default
int y; // public by default
private:
void validate(); // explicitly private
};Important: This is the only difference! Both classes and structs can:
- Contain any type of member (data, functions, nested types)
- Support inheritance and polymorphism
- Have constructors, destructors, and member functions
- Use all access specifiers
The only extra difference, for the pedantic folks out there, is that the default inheritance for structs is public, while the default inheritance for classes is private.
Access Specifiers
Access specifiers control the visibility and accessibility of class members. They enforce encapsulation by restricting how external code can interact with class internals.
Three Access Levels:
cpp
class Example {
private:
// Private members: accessible ONLY within this class
int privateData;
void privateMethod() { /* ... */ }
protected:
// Protected members: accessible within this class AND derived classes
int protectedData;
void protectedMethod() { /* ... */ }
public:
// Public members: accessible EVERYWHERE
int publicData;
void publicMethod() { /* ... */ }
};1. Private Access (Default for Classes)
cpp
class BankAccount {
private:
double balance; // Private data member
std::string accountNumber; // Private data member
void validateAmount(double amount) { // Private helper method
if (amount < 0) {
throw std::invalid_argument("Amount cannot be negative");
}
}
public:
void deposit(double amount) {
validateAmount(amount); // Can access private method
balance += amount;
}
double getBalance() const {
return balance; // Can access private data
}
};
// Usage
BankAccount account;
account.deposit(100.0); // OK: public method
double bal = account.getBalance(); // OK: public method
// account.balance = 1000.0; // ERROR: private member
// account.validateAmount(50); // ERROR: private methodKey Points:
- Default access level for classes
- Only accessible within the class
- Enforces encapsulation - external code cannot modify internal state
- Helper methods are often private
2. Protected Access
cpp
class BankAccount {
protected:
double balance; // Protected data member
std::string accountNumber; // Protected data member
void validateAmount(double amount) { // Protected method
if (amount < 0) {
throw std::invalid_argument("Amount cannot be negative");
}
}
public:
virtual void deposit(double amount) {
validateAmount(amount);
balance += amount;
}
};
class SavingsAccount : public BankAccount {
public:
void addInterest(double rate) {
// Can access protected members from base class
double interest = balance * rate;
balance += interest; // OK: balance is protected
validateAmount(interest); // OK: method is protected
}
void transferTo(BankAccount& other, double amount) {
validateAmount(amount);
if (balance >= amount) {
balance -= amount; // OK: can access protected balance
other.deposit(amount); // OK: deposit is public
}
}
};
// Usage
SavingsAccount savings;
savings.addInterest(0.05); // OK: public method
// savings.balance = 1000; // ERROR: protected member (not accessible from outside)Key Points:
- Accessible within the class AND derived classes
- Useful for inheritance - allows derived classes to access base class internals
- Still maintains encapsulation from external code
- Common for data members that derived classes need to access
3. Public Access (Default for Structs)
cpp
class BankAccount {
public:
double balance; // Public data member
std::string accountNumber; // Public data member
void deposit(double amount) {
if (amount < 0) {
throw std::invalid_argument("Amount cannot be negative");
}
balance += amount;
}
double getBalance() const {
return balance;
}
};
// Usage
BankAccount account;
account.balance = 1000.0; // OK: public member
account.accountNumber = "123"; // OK: public member
account.deposit(500.0); // OK: public methodKey Points:
- Accessible everywhere
- Default access level for structs
- Part of the public interface - external code can access and modify
- Use carefully - public data members can break encapsulation
Using Access Specifiers Effectively
Private: Use for Internal Implementation
cpp
class Vector3D {
private:
double x, y, z; // Internal data representation
void normalize() { // Internal helper method
double length = std::sqrt(x*x + y*y + z*z);
if (length > 0) {
x /= length;
y /= length;
z /= length;
}
}
public:
Vector3D(double x, double y, double z) : x(x), y(y), z(z) {}
void setLength(double length) {
normalize(); // Use private helper
x *= length;
y *= length;
z *= length;
}
double getX() const { return x; }
double getY() const { return y; }
double getZ() const { return z; }
};Use private for:
- Data members that shouldn't be directly modified
- Helper methods that are implementation details
- Internal state that needs protection
Protected: Use for Inheritance Support
cpp
class Shape {
protected:
double area; // Derived classes need to access this
virtual void calculateArea() = 0; // Derived classes must implement
public:
virtual double getArea() const { return area; }
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {
calculateArea(); // Can call protected method
}
protected:
void calculateArea() override {
area = M_PI * radius * radius; // Can access protected area
}
};Use protected for:
- Data members that derived classes need to access
- Methods that derived classes should override
- Implementation details shared with inheritance hierarchy
Public: Use for External Interface
cpp
class BankAccount {
public:
// Constructor - part of public interface
BankAccount(const std::string& owner, double initialBalance);
// Public methods - external interface
void deposit(double amount);
bool withdraw(double amount);
double getBalance() const;
std::string getOwner() const;
// Destructor - part of public interface
~BankAccount();
private:
// Internal implementation
double balance;
std::string owner;
std::vector<Transaction> transactionHistory;
void logTransaction(const Transaction& t);
bool validateWithdrawal(double amount);
};Use public for:
- Constructor and destructor
- Methods that external code should call
- Interface that defines how the class is used
- Constants that should be accessible
When to Use Classes vs Structs
Use Structs When:
cpp
// Simple data containers
struct Point2D {
double x, y; // Public by default - simple access
Point2D(double x, double y) : x(x), y(y) {}
double distanceTo(const Point2D& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx*dx + dy*dy);
}
};
// POD (Plain Old Data) types
struct Employee {
std::string name;
int id;
double salary;
std::string department;
};
// Configuration structures
struct GameSettings {
int screenWidth = 1920;
int screenHeight = 1080;
bool fullscreen = false;
double volume = 1.0;
std::string language = "English";
};Choose structs for:
- Simple data containers with no complex behavior
- POD types that need to be compatible with C code
- Configuration objects where all data should be accessible
- When you want public access by default
Use Classes When:
cpp
class BankAccount {
private:
double balance;
std::string accountNumber;
std::vector<Transaction> history;
void validateAmount(double amount);
void logTransaction(const Transaction& t);
public:
BankAccount(const std::string& accountNum, double initialBalance);
void deposit(double amount);
bool withdraw(double amount);
double getBalance() const;
~BankAccount();
};Choose classes for:
- Complex objects with behavior and state
- When you want encapsulation by default
- Objects that need to maintain invariants
- When you want private access by default
Best Practices
1. Start with Private, Make Public as Needed
cpp
class TemperatureSensor {
private:
double currentTemp;
double minTemp;
double maxTemp;
void validateTemperature(double temp);
void logTemperatureChange(double oldTemp, double newTemp);
public:
TemperatureSensor(double min, double max);
double getTemperature() const { return currentTemp; }
bool setTemperature(double temp);
bool isInRange() const;
};2. Use Getters and Setters for Data Access
cpp
class Person {
private:
std::string name;
int age;
public:
// Getters
const std::string& getName() const { return name; }
int getAge() const { return age; }
// Setters with validation
void setName(const std::string& newName) {
if (!newName.empty()) {
name = newName;
}
}
void setAge(int newAge) {
if (newAge >= 0 && newAge <= 150) {
age = newAge;
}
}
};3. Keep Public Interface Minimal
cpp
class DatabaseConnection {
private:
std::string connectionString;
bool isConnected;
std::mutex connectionMutex;
void establishConnection();
void validateConnection();
void logConnectionAttempt(bool success);
public:
DatabaseConnection(const std::string& connStr);
bool connect();
void disconnect();
bool executeQuery(const std::string& query);
~DatabaseConnection();
};4. Use Protected for Inheritance
cpp
class BaseClass {
protected:
int protectedData;
virtual void protectedMethod() {
// Implementation that derived classes can override
}
public:
virtual void publicMethod() {
protectedMethod(); // Call protected method
}
};
class DerivedClass : public BaseClass {
public:
void derivedMethod() {
protectedData = 42; // Can access protected data
protectedMethod(); // Can call protected method
}
};Summary
Classes and structs are the foundation of object-oriented programming in C++:
Key Differences:
- Classes: Private members by default, use for encapsulation
- Structs: Public members by default, use for simple data containers
Access Specifiers:
- Private: Only accessible within the class (default for classes)
- Protected: Accessible within class and derived classes
- Public: Accessible everywhere (default for structs)
Best Practices:
- Start with private and make public as needed
- Use classes when you want encapsulation by default
- Use structs when you want public access by default
- Keep public interface minimal and well-designed
- Use protected for inheritance scenarios
When to Use Each:
- Structs: Simple data containers, POD types, configuration objects
- Classes: Complex objects with behavior, when encapsulation is needed
- Private: Internal implementation details
- Protected: Members that derived classes need to access
- Public: External interface and constructors/destructors
Understanding these concepts provides the foundation for building robust, maintainable C++ applications using object-oriented principles.
Questions
Q: What is the main difference between a class and a struct in C++?
The main difference is the default access level. In a class, members are private by default, while in a struct, members are public by default. This is the only difference - both can contain any type of member, support inheritance, and have the same functionality.
Q: What happens when you try to access a private member from outside the class?
Accessing a private member from outside the class causes a compilation error. The compiler enforces access control at compile time, preventing you from violating encapsulation. This is a fundamental feature of C++ that helps maintain data integrity and design intent.
Q: Which access specifier allows derived classes to access members but prevents external access?
The protected access specifier allows derived classes to access members while preventing external access. This provides a middle ground between public (accessible everywhere) and private (accessible only within the class), making it ideal for inheritance scenarios.
Q: What is the purpose of access specifiers in C++?
Access specifiers enforce encapsulation by controlling how class members can be accessed. They help maintain data integrity, prevent unauthorized access to internal state, and make the class interface clear and predictable. This is a fundamental principle of object-oriented programming.
Q: When should you use a struct instead of a class in C++?
Use a struct when you want all members to be public by default, typically for simple data containers or POD (Plain Old Data) types. Use a class when you want to enforce encapsulation with private members by default. The choice is mainly about expressing intent and default behavior.