C++
Warmup Quiz
- What will be the output of the following code?
#include <iostream>
int main() {
int x = 5;
int* p = &x;
*p = 10;
std::cout << x << '\n';
return 0;
}
-
What does the
const
keyword do when applied to a variable? -
What is the difference between
struct
andclass
in C++? -
What is the purpose of a constructor in a class?
-
Explain the difference between a pointer and a reference.
-
What will be the output of the following code?
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
-
In your own words, explain what the Standard Template Library (STL) is.
-
What will be the output of the following code?
#include <iostream>
class Base {
public:
virtual void print() {
std::cout << "Base class\n";
}
};
class Derived : public Base {
public:
void print() override {
std::cout << "Derived class\n";
}
};
int main() {
Base* b = new Derived();
b->print();
delete b;
return 0;
}
-
Explain the difference between
std::array<T, N>
andstd::vector<T>
. -
Explain the output of the following lambda-based code.
#include <iostream>
int main() {
int a = 10, b = 20;
auto sum = [&]() -> int { return a + b; };
b = 30;
std::cout << sum() << '\n';
return 0;
}
Revisiting Fundamentals
Functions and Pointers
Functions are the building blocks of C++ programs, and pointers are fundamental for memory management. Let’s revisit these concepts with an example.
#include <iostream>
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
std::cout << "Before swap: x = " << x << ", y = " << y << '\n';
swap(&x, &y);
std::cout << "After swap: x = " << x << ", y = " << y << '\n';
return 0;
}
Discussion Points:
- What happens when you pass pointers versus values?
- When would you use references instead of pointers?
Object-Oriented Programming (OOP)
Object-oriented programming is key to structuring large projects in C++. Let’s review how classes and inheritance work.
#include <iostream>
#include <string>
class BankAccount {
private:
std::string owner;
double balance;
public:
BankAccount(const std::string& owner, double balance)
: owner(owner), balance(balance) {}
void deposit(double amount) {
balance += amount;
}
void withdraw(double amount) {
if (amount <= balance)
balance -= amount;
else
std::cout << "Insufficient funds!\n";
}
void display() const {
std::cout << owner << "'s balance: $" << balance << '\n';
}
};
int main() {
BankAccount account("John Doe", 1000.0);
account.display();
account.deposit(500);
account.withdraw(300);
account.display();
return 0;
}
Discussion Points:
- What is the purpose of the
private
keyword? - How does the
const
qualifier ensure safety indisplay()
?
Modern C++ Features
Raw pointers are error-prone. Smart pointers, introduced in C++11, simplify memory management.
std::unique_ptr
:
- Exclusive ownership: only one
std::unique_ptr
can point to a resource at a time.
#include <iostream>
#include <memory>
#include <string>
class MyClass {
public:
explicit MyClass(std::string name) : name_{std::move(name)} { std::cout << "Constructor called " << name_ << std::endl; }
~MyClass() { std::cout << "Destructor called " << name_ << std::endl; }
private:
std::string name_;
};
int main() {
{ // Create a scope to demonstrate smart pointer behavior
std::unique_ptr<MyClass> u_ptr = std::make_unique<MyClass>("unique");
MyClass* raw_ptr = new MyClass("raw");
} // end of scope; u_ptr goes out of scope, destructor is called automatically
// raw_ptr is not deleted, causing a potential memory leak
return 0;
}
std::shared_ptr
:
- Shared ownership: multiple
std::shared_ptr
can point to the same resource. - Reference counting: the resource is deleted when the last
std::shared_ptr
goes out of scope.
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
};
int main() {
std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>();
std::cout << "Use count: " << sp1.use_count() << std::endl;
{
std::shared_ptr<MyClass> sp2 = sp1;
std::cout << "Use count: " << sp1.use_count() << std::endl;
}
std::cout << "Use count: " << sp1.use_count() << std::endl;
return 0;
}
std::weak_ptr
- Weak reference: does not affect the reference count of the shared resource.
- Doesn’t increase the reference count.
- Used to prevent circular references in shared ownership.
#include <iostream>
#include <memory>
class NodeB; // forward declaration
class NodeA {
public:
std::shared_ptr<NodeB> strong_ptr; // Strong reference to NodeB
std::weak_ptr<NodeB> weak_ptr; // Weak reference to NodeB
NodeA() { std::cout << "NodeA constructor\n"; }
~NodeA() { std::cout << "NodeA destructor\n"; }
};
class NodeB {
public:
std::shared_ptr<NodeA> strong_ptr; // Strong reference to NodeA
std::weak_ptr<NodeA> weak_ptr; // Weak reference to NodeA
NodeB() { std::cout << "NodeB constructor\n"; }
~NodeB() { std::cout << "NodeB destructor\n"; }
};
int main() {
{ // create scope
std::cout << "Entering first scope..." << std::endl;
// Create NodeA and NodeB, each referencing the other.
auto a = std::make_shared<NodeA>();
auto b = std::make_shared<NodeB>();
a->strong_ptr = b; // NodeA has a strong reference to b
b->strong_ptr = a; // NodeB has a strong reference to a
std::cout << "Exiting first scope..." << std::endl;
} // end scope
// Here, a and b go out of scope, but each Node holds a strong pointer to the other.
// Their reference counts never reach zero, so destructors are NOT called.
// This leads to a memory leak because NodeA and NodeB remain alive, referencing each other.
{ // create new scope
std::cout << "Entering second scope..." << std::endl;
auto a = std::make_shared<NodeA>();
auto b = std::make_shared<NodeB>();
a->strong_ptr = b; // NodeA has a strong reference to b
b->weak_ptr = a; // NodeB has a weak reference to a
std::cout << "Exiting second scope..." << std::endl;
}
return 0;
}
Discussion Points:
- What happens when the
std::unique_ptr
goes out of scope? - Compare
std::shared_ptr
andstd::unique_ptr
. - When should you use
std::weak_ptr
? - Should we use raw pointers in modern C++? — Generally, no.
Functions as Objects
Lambda Functions
Lambda functions (also called lambda expressions) in C++ are unnamed (anonymous) functions that you can define inline. They were introduced in C++11 to make it easier to create small, concise functions, especially for use with the Standard Template Library (STL) algorithms or as callbacks. Unlike regular functions, they can capture variables from their surrounding scope. This is incredibly useful for passing context to a function on the fly.
Syntax:
[ capture_list ] ( parameter_list ) -> return_type {
// function body
}
- capture_list: Which variables from the enclosing scope are available inside the lambda and how they are captured (by value, by reference, etc.).
- parameter_list: The parameters the lambda accepts (similar to a function’s parameter list).
- return_type: Often omitted because it can be deduced by the compiler, but can be specified explicitly using
-> return_type
. - function body: The code that executes when the lambda is called.
Example of lambda function usage:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {5, 2, 8, 3, 1};
std::sort(nums.begin(), nums.end(), [](int a, int b) { return a < b; });
for (int num : nums) {
std::cout << num << ' ';
}
return 0;
}
Discussion Points:
- How does the lambda function work in
std::sort
? - When should you use lambdas over named functions?
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> values = {1, 2, 3, 4, 5};
int offset = 10;
auto printValue = [](int val) {
std::cout << val << " ";
};
// Capture everything by value (copy)
std::for_each(values.begin(), values.end(), [=](int val) {
// Modifies a copy of 'val', not the element itself
int tmp = val + offset;
(void)tmp; // suppress unused-variable warning in this snippet
// offset += 1; // error: 'offset' cannot be modified; use [=]() mutable { ... } to allow modification
});
std::for_each(values.begin(), values.end(), printValue);
// Capture everything by reference
std::for_each(values.begin(), values.end(), [&](int& val) {
val += offset; // modifies 'val' directly in the vector via reference
offset += 1;
});
std::for_each(values.begin(), values.end(), printValue);
std::cout << std::endl;
return 0;
}
std::function
A flexible, type-erased wrapper that can store function pointers, lambdas, or functor objects. It is part of the C++ Standard Library and is useful for creating callbacks or function objects that can be passed around like variables.
#include <iostream>
#include <functional>
int sum(int a, int b) {
return a + b;
}
int main() {
std::function<int(int, int)> func1 = sum;
std::function<int(int, int)> func2 = [](int a, int b) { return a * b; };
std::cout << "sum(3, 4): " << func1(3, 4) << std::endl;
std::cout << "multiply(3, 4): " << func2(3, 4) << std::endl;
return 0;
}
Coding Challenge
Task: Create a simple program to manage student records, including adding and displaying students.
- Use a
Student
class with properties for name, age, and grades. - Store students in a
std::vector
. - Implement a menu-driven program for user interaction.
#include <iostream>
#include <vector>
#include <string>
class Student {
private:
std::string name;
int age;
std::vector<int> grades;
public:
Student(const std::string& name, int age) : name(name), age(age) {}
void addGrade(int grade) {
grades.push_back(grade);
}
void display() const {
std::cout << "Name: " << name << ", Age: " << age << ", Grades: ";
for (int grade : grades) {
std::cout << grade << ' ';
}
std::cout << '\n';
}
};
int main() {
std::vector<Student> students;
// Add menu-driven functionality here
return 0;
}