0% found this document useful (0 votes)
38 views12 pages

02-Class CPP Tutorial

This C++ Programming Tutorial covers advanced topics including class methods, access specifiers, encapsulation, friend functions, inheritance, and polymorphism. It provides real-world problem scenarios, best practices, common pitfalls, and practical exercises to enhance understanding. The tutorial is structured to guide developers through mastering these concepts for effective C++ programming.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
38 views12 pages

02-Class CPP Tutorial

This C++ Programming Tutorial covers advanced topics including class methods, access specifiers, encapsulation, friend functions, inheritance, and polymorphism. It provides real-world problem scenarios, best practices, common pitfalls, and practical exercises to enhance understanding. The tutorial is structured to guide developers through mastering these concepts for effective C++ programming.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

C++ Programming Tutorial

Mastering Class Methods, Access Specifiers, Encapsulation, Friend Functions,


Inheritance, and Polymorphism
Table of Contents
Explore these advanced C++ topics with real-world problem scenarios and solutions

1. C++ Class Methods


Static, Const, Inline, and Overloading

2. C++ Access Specifiers


Private, Protected, and Public

3. C++ Encapsulation
Data Hiding, Getters/Setters, and Design Patterns

4. C++ Friend Functions


Friend Functions, Friend Classes, and Use Cases

5. C++ Inheritance
Single, Multiple, Virtual, and Diamond Problem

6. C++ Polymorphism
Virtual Functions, Abstract Classes, and RTTI

7. Best Practices & Common Pitfalls


Code Quality, Performance, and Modern C++ Idioms

8. Practical Exercises
Challenge Your Knowledge

9. Discussion Points
Advanced Topics for Senior Developers

10. Summary & Conclusion


Key Takeaways and Next Steps
C++ Class Methods: Static, Const, Inline, and Overloading

 Real-World Problem  Best Practices


Designing a high-performance math utilities class for a high-frequency trading system where: staticUse for stateless utilities that don't depend on object state or for class-

• Methods must optimize for speed and minimal memory allocation wide shared data.
• Some calculations are stateless, others maintain state
 const Mark methods as const when they don't modify object state—improves
• Some operations need multiple implementations for different data types
code safety and enables optimizations.

 inline Use for small, frequently called methods where function call overhead
 Implementation matters. Modern compilers often make this decision automatically.

 Provide consistent interfaces for similar operations with different


overloading
class TradingMath {
private: parameter types/counts.
double m_volatilityFactor;
std::vector<double> m_priceHistory;

public:  Common Pitfalls


// Static method - independent of object state
static double calculateBlackScholes(  Static methods can't access non-static members — leads to compilation errors or
double s, double k, unexpected behavior.
double t, double v,
double r) {  Missing const correctness — prevents passing objects to const references and
// Implementation breaks const propagation.
return result;
 Overusing inline — can bloat binary size in headers; trust compiler optimization
}
instead.
// Const method - guarantees no state change  Ambiguous overloads — multiple methods that can accept similar types cause
double getVolatility() const { compiler errors.
return m_volatilityFactor;
}

// Inline method - suggests compiler optimization


 Discussion Points
inline double quickPrice(double base) {
return base * (1.0 + m_volatilityFactor);  How would you decide between free functions and static methods?
}
 When does method overloading improve or harm API usability?
// Method overloading - same name, different params
void updateModel(double newPrice) {  Interactive: Refactor a trading algorithm to use appropriate method types
m_priceHistory.push_back(newPrice);
// Update model with single price
}

void updateModel(const std::vector<double>& prices) {


// Update model with batch of prices
for (auto price : prices) {
m_priceHistory.push_back(price);
}
}
};
C++ Access Specifiers: Private, Protected, and Public

 Real-World Problem  Best Practices


Securing sensitive business logic in a shared financial application codebase where: private Hide implementation details that clients don't need to know and should

• Certain calculations must remain proprietary and protected from misuse not directly manipulate.
• Multiple teams need different levels of access to the same components protected Share functionality with derived classes while keeping it hidden from

• External API interfaces need to expose functionality without revealing implementation
external clients.

 Expose only what's necessary in your interface—follow the principle of


public
 Implementation minimum necessary exposure.

class SecurePaymentProcessor {  Prefer non-member non-friend functions when possible to further reduce
private: coupling and exposure.
// Only accessible within this class
std::string m_encryptionKey;
double m_transactionFee;
 Common Pitfalls
std::string encryptData(const std::string& data) {
// Proprietary encryption algorithm  Over-exposing implementation details makes it harder to change internals
return encrypted_result; without breaking client code.
}
 Making data members public for convenience prevents enforcing invariants and
protected: validations.
// Accessible by this class and derived classes
 Over-using protected creates tight coupling between base and derived classes,
bool validateTransaction(const Transaction& tx) {
hindering independent evolution.
// Common validation logic
return isValid;  Assuming privacy equals security — access modifiers are for maintainability, not
} security (compiled code still exposed).

void logActivity(const std::string& action) {


// Logging mechanism for all payment types
}
 Discussion Points
public:  When is it justified to break encapsulation for testability?
// Accessible by anyone
SecurePaymentProcessor(double fee)  How do access modifiers affect API stability and versioning?
: m_transactionFee(fee) {
m_encryptionKey = generateSecureKey();  Should protected be used at all in modern C++ design?
}
 Alternative approaches: PIMPL idiom, opaque pointers, etc.
bool processPayment(const Payment& payment) {
// Public interface for payment processing
if (!validateTransaction(payment.transaction)) {
return false;
}
std::string secure_data = encryptData(payment.data);
// Process payment using secure data
logActivity("Payment processed");
return true;
}
};
C++ Encapsulation: Data Hiding, Getters/Setters, & Patterns

 Real-World Problem  Best Practices


Designing a secure BankAccount API for a financial system where: private Hide implementation details to maintain invariants and prevent direct

• Balance must be protected from unauthorized modifications tampering.
• Account operations need validation before execution
 public Provide a minimal, clear API that enforces business rules and
• API must be intuitive but prevent invariant violations
validation.
• Transaction history must be maintained but immutable
 getters Use const getters for safe read-only access to properties.

 Implementation  patterns Consider fluent interfaces for operations that can be chained.

 Return success/failure from operations that might fail validation.


class BankAccount {
private:
// Data hiding - private members cannot be accessed directly
std::string m_accountNumber;
double m_balance;
 Common Pitfalls
std::vector<Transaction> m_transactions;
 Automatic getters/setters for everything — defeats the purpose of
bool m_frozen;
encapsulation by just moving direct access through methods.
// Private validation method  Returning non-const references to private members allows modification
bool canWithdraw(double amount) const { outside the class.
return !m_frozen && m_balance >= amount;
}  Exposing internal state through overly detailed getters, breaking invariants.

public:  Overlooking thread safety in mutator methods when objects are shared.
// Constructor initializes with invariants
BankAccount(const std::string& accNum, double initialBalance = 0.0
: m_accountNumber(accNum), m_balance(initialBalance), m_frozen
 Design Pattern Example
// Getter methods - read-only access to private data
double getBalance() const { // Immutable transaction record pattern
return m_balance; class Transaction {
} private:
const std::string m_type;
const double m_amount;
const std::string& getAccountNumber() const { const std::time_t m_timestamp;
return m_accountNumber;
} public:
Transaction(std::string type, double amount)
: m_type(std::move(type)),
bool isFrozen() const { m_amount(amount),
return m_frozen; m_timestamp(std::time(nullptr)) {}
}
// Read-only accessors, no setters at all
const std::string& type() const { return m_type; }
// Controlled mutation with validation double amount() const { return m_amount; }
bool deposit(double amount) { std::time_t timestamp() const { return m_timestamp; }
if (amount <= 0 || m_frozen) return false; };

This immutable object pattern ensures transaction records can't be altered after creation - perfect for audit
m_balance += amount; trails.
m_transactions.push_back(Transaction("deposit", amount));
return true;
}
 Exercise
// Fluent interface pattern - method chaining Refactor this anti-pattern to proper encapsulation:
BankAccount& freeze() { class BadAccount {
m_frozen = true; public:
return *this; double balance; // Directly accessible!
} void setBalance(double b) { balance = b; }
};

BankAccount& unfreeze() {
m_frozen = false;
return *this;
}
};
C++ Friend Functions & Classes: When and Why?

 Real-World Scenario  Best Practices


Building a serialization framework that needs to access private members across multiple classes: friend function Use for operators and free functions that need direct access to

• Need efficient JSON serialization without exposing internal implementation encapsulated data.
• Complex data types require custom operators for I/O streams friend class Use for utility classes that work closely with the target class

• Need to grant specific access without breaking encapsulation globally
(serializers, factories, unit tests).

 Implement operators as friends when they need access to private


operators
 Implementation members of both operands.

// Forward declaration for friendship  Forward declare friend classes to reduce compile-time dependencies.
class JsonSerializer;

class TradeOrder {
private:  Common Pitfalls
int m_orderId;
double m_price;  Overusing friendship — creates tight coupling and breaks encapsulation
std::string m_symbol; principles.
bool m_isBuyOrder;
 Friendship is not inherited — derived classes don't inherit friendship
// Friend function - can access private members relationships.
friend std::ostream& operator<<(
 Friendship is not transitive — if A is friend of B, and B is friend of C, A is not
std::ostream& os, const TradeOrder& order);
automatically friend of C.
// Friend class - entire class can access private members  Friendship breaks unit testing isolation — harder to mock behavior when
friend class JsonSerializer; implementation is exposed.

public:
TradeOrder(int id, double price,
const std::string& symbol,
 Discussion Points
bool isBuy)
: m_orderId(id), m_price(price),  When is friend justifiable versus using public accessor methods?
m_symbol(symbol), m_isBuyOrder(isBuy) {}
};  How would you refactor to minimize friend declarations?

// Friend function implementation - direct access to private  Demo: Implementing stream operators for complex financial types
std::ostream& operator<<(std::ostream& os,
const TradeOrder& order) {  What alternatives exist when friendship causes maintenance problems?
return os << "Order #" << order.m_orderId
<< " [" << order.m_symbol << "]: "
<< (order.m_isBuyOrder ? "BUY" : "SELL")
<< " at $" << order.m_price;
}

// Friend class implementation


class JsonSerializer {
public:
static std::string serialize(
const TradeOrder& order) {
// Direct access to private members
return "{\n"
" \"id\": " + std::to_string(order.m_orderId) + ",\n"
" \"symbol\": \"" + order.m_symbol + "\",\n"
" \"price\": " + std::to_string(order.m_price) + ",\n"
" \"side\": \"" +
(order.m_isBuyOrder ? "buy" : "sell") + "\"\n"
"}";
}
};
C++ Inheritance: Single, Multiple, Virtual, and the Diamond Problem

 Real-World Problem  Best Practices


Designing a plugin system for a graphics application where: single Prefer composition over inheritance when possible to avoid deep

• Multiple features inherit from a common interface hierarchies and tight coupling.
• Some features need functionality from multiple parent classes
 multipleUse interfaces (pure abstract classes) for multiple inheritance to
• Ambiguity arises when classes share common ancestors (Diamond Problem)
minimize state conflicts.
• Memory overhead becomes a concern with deep inheritance hierarchies
 virtualUse virtual inheritance when inheriting from shared base classes to avoid
the diamond problem.
 The Diamond Problem
 diamond Always provide explicit initialization for virtual base classes in the most
Base derived constructor.

Derived1 Derived2  Common Pitfalls

 Object slicing when passing derived objects by value instead of by


reference/pointer.
Diamond
 Ambiguous member access without using scope resolution operator (::) in
Without virtual inheritance, the Diamond class inherits Base twice! multiple inheritance.

 Implementation  Performance overhead of virtual inheritance due to extra pointer indirections.

 Forgetting to mark destructors as virtual in base classes, leading to resource


// The Diamond Problem Solution leaks.
class Base {
protected:
int data;
public:  Implementation Trade-offs
Base(int d) : data(d) {}
virtual void display() {  single + Simple, straightforward
std::cout << "Base: " << data << std::endl; - Limited to one parent's functionality
}
};  multiple + Access to multiple parents' features
- Potential ambiguity and complexity
// Virtual inheritance prevents duplicate Base
class Derived1 : virtual public Base {  virtual+ Solves diamond problem
public: - Performance and size overhead
Derived1(int d) : Base(d) {}
void display() override {
std::cout << "Derived1: " << data << std::endl;
}  Discussion Points
};
 When would you choose inheritance vs. composition in your designs?
class Derived2 : virtual public Base {
public:  How does virtual inheritance impact binary size and performance?
Derived2(int d) : Base(d) {}
 Exercise: Refactor a legacy class hierarchy to eliminate the diamond problem
void display() override {
std::cout << "Derived2: " << data << std::endl;
}
};

// Diamond inherits from both Derived classes


class Diamond : public Derived1, public Derived2 {
public:
// Must explicitly initialize the virtual base
Diamond(int d) : Base(d), Derived1(d), Derived2(d) {}
void display() override {
std::cout << "Diamond: " << data << std::endl;
}
};
C++ Polymorphism: Virtual Functions, Abstract Classes, RTTI

 Real-World Problem  Best Practices


Designing a plugin system for an extensible data processing application where: virtual Always declare destructors virtual in base classes to ensure proper

• Third-party developers can create custom data processors cleanup of derived objects.
• The core application must handle plugins without knowing their specific types
 abstract Use pure virtual functions to define interfaces and enforce
• Runtime validation of plugin capabilities is required
implementation in derived classes.
• Plugins need common interfaces with specialized implementations
 RTTI Use dynamic_cast for type safety when downcasting is necessary, but
prefer virtual functions when possible.
 Implementation
 Use override specifier to catch mismatched function signatures during
compilation.
// Abstract base class with pure virtual functions
class DataProcessor {
public:
// Virtual destructor - essential for polymorphic classes
virtual ~DataProcessor() = default;  Common Pitfalls

// Pure virtual functions define the interface  Object slicing — assigning derived objects to base objects by value loses derived
virtual bool canProcess(const std::string& dataType) const = 0; class data and behavior.
virtual void process(std::vector<double>& data) = 0;
 Missing virtual destructors — causes memory leaks when deleting derived
objects through base pointers.
// Virtual function with default implementation
virtual std::string getVersion() const {  Overusing RTTI (typeid, dynamic_cast) — often indicates design problems;
return "1.0"; prefer polymorphic interfaces.
}
};  Forgetting the override keyword — can lead to subtle bugs when base class
interfaces change.
// Concrete implementation of the interface
class ImageProcessor : public DataProcessor {
private:
std::string m_processorName;  Discussion Points
double m_qualityFactor;
 When should you use pure polymorphism versus RTTI with dynamic_cast?
public:
ImageProcessor(const std::string& name, double quality)  Performance implications of virtual function calls in high-performance systems
: m_processorName(name), m_qualityFactor(quality) {}
 Modern alternatives: type erasure, std::variant, std::visit vs. classic polymorphism
bool canProcess(const std::string& dataType) const override {
 Interactive: Identify and fix polymorphism bugs in a plugin architecture
return dataType == "image" || dataType == "bitmap";
}

void process(std::vector<double>& data) override {


// Image processing implementation  Under the Hood
} How polymorphism works internally:
};
• vtable: Compiler creates a lookup table of virtual functions
// Plugin manager using polymorphism • vptr: Each object contains a hidden pointer to its class's vtable
class PluginManager {
• Dynamic dispatch: Function calls resolved at runtime via vtable lookup
private:
std::vector<std::unique_ptr<DataProcessor>> m_plugins; • RTTI metadata: Additional type information stored for dynamic_cast/typeid

public:
void registerPlugin(std::unique_ptr<DataProcessor> plugin) {
m_plugins.push_back(std::move(plugin));
}

void processData(const std::string& dataType,


std::vector<double>& data) {
for (auto& plugin : m_plugins) {
if (plugin->canProcess(dataType)) {
// Polymorphic call - resolved at runtime
plugin->process(data);
break;
}
}
}

// Using RTTI for type-safe casting


template <typename T>
T* getPluginAs(size_t index) {
if (index >= m_plugins.size()) return nullptr;

// dynamic_cast returns nullptr if cast fails


return dynamic_cast<T*>(m_plugins[index].get());
}
};
C++ Best Practices & Common Pitfalls

 Common Challenges  Best Practices


Senior developers often face these challenges in large C++ codebases: Rule of Five Implement all five special member functions (destructor, copy

• Memory leaks and resource management issues constructor, copy assignment, move constructor, move assignment) or none.
• Poor performance due to unnecessary copying
 RAII Resource Acquisition Is Initialization - acquire resources in constructor,
• Brittle code that's difficult to maintain
release in destructor to prevent leaks.
• Legacy patterns that don't leverage modern C++
 Smart Pointers Use smart pointers instead of raw pointers - std::unique_ptr for
exclusive ownership, std::shared_ptr for shared resources.
 Modern Implementations
 Modern C++Prefer auto for complex types, use nullptr instead of NULL, leverage
range-based for loops and constexpr when possible.
// Rule of Five implementation
class ResourceHandler {
private:
Resource* m_resource;
 Common Pitfalls
public:
// Constructor with RAII  Memory leaks from not following RAII or improper resource management.
ResourceHandler() : m_resource(new Resource()) {}
 Dangling pointers from deleted objects or transferred ownership.
// Destructor  Circular references with std::shared_ptr (solution: use std::weak_ptr).
~ResourceHandler() { delete m_resource; }
 Using exceptions incorrectly - not having strong exception guarantees.
// Copy constructor
ResourceHandler(const ResourceHandler& other)  Object slicing when copying derived classes to base class objects.
: m_resource(new Resource(*other.m_resource)) {}

// Copy assignment
ResourceHandler& operator=(const ResourceHandler& other) {  Find the Bug Challenge
if (this != &other) {
delete m_resource; class DataProcessor {
m_resource = new Resource(*other.m_resource); int* data;
} public:
DataProcessor(size_t size) {
return *this; data = new int[size];
} }
~DataProcessor() { delete data; }
// Move constructor DataProcessor(const DataProcessor& other) {
data = other.data;
ResourceHandler(ResourceHandler&& other) noexcept }
: m_resource(other.m_resource) { };
other.m_resource = nullptr;
} What memory-related issues does this code have? How would you fix them using modern C++
practices?
// Move assignment
ResourceHandler& operator=(ResourceHandler&& other) noexcept {
if (this != &other) {
delete m_resource;
m_resource = other.m_resource;
other.m_resource = nullptr;
}
return *this;
}
};

// Modern approach with smart pointers


class ModernResourceHandler {
private:
std::unique_ptr<Resource> m_resource;

public:
// Constructor - RAII handled automatically
ModernResourceHandler()
: m_resource(std::make_unique<Resource>()) {}

// Rule of Zero - compiler generates correct


// special member functions automatically
};
Practical Exercises: Challenge Your Knowledge

Test your understanding of advanced C++ concepts with these real-world coding challenges. Try to solve them before looking at the solutions.

 Encapsulation Refactor Easy  Diamond Problem Fix Medium

Refactor this poorly encapsulated class to use proper access control and data hiding: Fix the diamond inheritance problem in this class hierarchy:

class UserAccount { class Device {


public: public:
std::string username; void powerOn() { /* ... */ }
std::string password; };
bool isLoggedIn;
int loginAttempts; class Printer : public Device {
public:
void Login(std::string pwd) { void print() { /* ... */ }
if (pwd == password) };
isLoggedIn = true;
else class Scanner : public Device {
loginAttempts++; public:
} void scan() { /* ... */ }
}; };

class MFP : public Printer, public Scanner {


// This causes the diamond problem!
};

 Polymorphism & Memory Hard  Friend Function Challenge Medium

Find and fix the memory leaks and polymorphism issues: Implement a proper stream insertion operator using friend function:

class Resource { class Complex {


public: private:
Resource() { /* Initialize resource */ } double real;
void use() { /* Use resource */ } double imag;
~Resource() { /* Clean up */ } public:
}; Complex(double r, double i)
: real(r), imag(i) {}
class ResourceManager {
private: // TODO: Implement friend function for
Resource* resources[10]; // std::ostream& operator<<(std::ostream&, const Complex&)
public: // that outputs in format "a+bi" or "a-bi"
ResourceManager() { };
for (int i = 0; i < 10; i++)
resources[i] = new Resource();
}
~ResourceManager() {
// Missing cleanup code
}
};
Discussion Points for Developers

Facilitate critical thinking and advanced debate among senior C++ developers. Consider these topics for group discussion, peer review sessions, or personal reflection on coding
philosophy.

 Friend Functions & Encapsulation Principles  Extensibility vs. Performance Design

When are friend functions truly justified, and when do they signal poor design? What design decisions would you make when balancing extensibility and performance?

"Friend functions break encapsulation and create tight coupling between classes. Are there "Abstract interfaces and virtual functions enable extensibility but introduce runtime overhead.
legitimate use cases where this violation is acceptable?" How do you decide when this trade-off is worth it?"

"What alternative designs would you suggest to avoid using friend functions while maintaining "How would you design a system that needs to be both highly extensible and performance-
performance and readability?" critical? Which C++ features would you leverage?"

"How do you balance the practical need for efficient operator overloading with the principle of "When is compile-time polymorphism (templates, CRTP) preferable to runtime polymorphism
strict encapsulation?" (virtual functions)?"

 Virtual Inheritance: Use or Avoid? Language  Performance Debugging & Optimization Performance

Is virtual inheritance a necessary tool or a sign of overly complex design? Share experiences benchmarking and optimizing complex C++ code:

"Virtual inheritance solves the diamond problem but adds complexity and runtime overhead. "What hidden performance bottlenecks have you discovered in C++ code that appeared correct
Is the problem itself an indication of a flawed design?" but performed poorly?"

"What alternative design patterns could you use to avoid multiple inheritance scenarios that "How do you balance modern C++ features (smart pointers, std::function, lambdas) with
lead to the diamond problem?" performance requirements in critical code paths?"

"How would you refactor a legacy codebase that heavily relies on complex inheritance "What tools and techniques have you found most effective for profiling memory usage and
hierarchies with multiple inheritance?" identifying leaks in complex C++ applications?"
namespace cpp_advanced {
Summary & Key Takeaways

Mastering these advanced C++ concepts will enable you to build robust, efficient, and maintainable software systems:

 Class Methods  Access Specifiers


Leverage static methods for utilities, const for safety, inline for performance, and overloading Control visibility with private, protected, and public to enforce data hiding and create clean
for intuitive APIs. interfaces.

 Encapsulation  Friend Functions


Hide implementation details, use getters/setters judiciously, and design cohesive classes with Use friend sparingly for operators and tightly coupled classes while maintaining
strong invariants. encapsulation boundaries.

 Inheritance  Polymorphism
Apply single, multiple, and virtual inheritance thoughtfully, and solve the diamond problem Design with virtual functions, abstract classes, and RTTI for flexible, extensible architectures
with virtual base classes. and runtime behavior.

 Modern C++ Mindset


Remember: Prefer composition over inheritance, use RAII for resource management, leverage smart pointers over raw pointers, and follow the Rule of Five for robust class design. These principles,
combined with the advanced topics we've covered, will help you create high-performance, maintainable C++ code for complex systems.

Continue your C++ journey by exploring templates, move semantics, concurrency, and modern C++ features.

You might also like