CPP Series-8: Advanced C++ Features
Introduction
In this article, we explore advanced C++ features such as templates and the Standard Template Library (STL). These tools enable generic programming and efficient data manipulation, enhancing the flexibility and performance of C++ applications.
Templates
Templates allow functions and classes to operate with generic types, promoting code reuse.
Function Templates
Function templates define generic functions that work with any data type.
Example Code: FunctionTemplate.
#include <iostream>
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << "Sum of integers: " << add<int>(3, 5) << std::endl;
std::cout << "Sum of doubles: " << add<double>(2.5, 3.7) << std::endl;
return 0;
}Explanation
- Template Parameter: typename T specifies a generic type parameter.
- Type Inference: The compiler deduces the appropriate type for the template parameter.
Compiler-Level Insights
- Template Instantiation: The compiler generates specific versions of templates for each type used.
- Code Bloat: Templates can lead to code bloat if many types are used. Optimize template usage to prevent excessive code size.
Common Errors and Solutions
- Type Constraints: Ensure template parameters support the required operations to prevent errors.
- Explicit Instantiation: Use explicit instantiation to control which types are instantiated, reducing code bloat.
Do’s and Don’ts
- Do: Use templates to create reusable code components.
- Don’t: Overcomplicate templates. Keep them simple and focused.
Class Templates
Class templates define generic classes for managing collections or data structures.
Example Code: ClassTemplate.
#include <iostream>
#include <vector>
template <typename T>
class Container {
private:
std::vector<T> elements;
public:
void add(const T& element) {
elements.push_back(element);
}
void display() const {
for (const auto& elem : elements) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
};
int main() {
Container<int> intContainer;
intContainer.add(1);
intContainer.add(2);
intContainer.display();
Container<std::string> stringContainer;
stringContainer.add("Hello");
stringContainer.add("World");
stringContainer.display();
return 0;
}Explanation
- Generic Container: The Container class manages elements of any type, demonstrating template use.
Compiler-Level Insights
- Template Specialization: Define specific behavior for certain types using template specialization.
- Type Checking: The compiler performs type checking for template instantiations, ensuring type safety.
Common Errors and Solutions
- Specialization: Use specialization for specific types when generic implementation is insufficient.
- Overhead: Minimize template overhead by avoiding unnecessary instantiations.
Do’s and Don’ts
- Do: Use class templates for collections and data structures.
- Don’t: Use templates for every class. Consider if a specific implementation is more appropriate.
Standard Template Library (STL)
The STL provides a collection of classes and functions for common data structures and algorithms.
STL Containers
STL containers manage collections of objects, offering various operations.
Example Code: STLContainers.
#include <iostream>
#include <vector>
#include <map>
int main() {
// Vector example
std::vector<int> numbers = {1, 2, 3, 4, 5};
numbers.push_back(6);
std::cout << "Vector: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// Map example
std::map<std::string, int> ageMap;
ageMap["Alice"] = 30;
ageMap["Bob"] = 25;
std::cout << "Alice's age: " << ageMap["Alice"] << std::endl;
return 0;
}Explanation
- std::vector: Dynamic array that can resize as elements are added or removed.
- std::map: Associative container that stores key-value pairs.
Compiler-Level Insights
- Iterator Validity: Be aware of iterator invalidation rules when modifying containers.
- Allocator Customization: Customize memory allocation for containers using allocators.
Common Errors and Solutions
- Iterator Invalidation: Use iterators carefully to avoid invalidation during modifications.
- Algorithm Efficiency: Choose the appropriate STL algorithms for efficient data manipulation.
Do’s and Don’ts
- Do: Use STL containers for standard data structures. They are well-optimized and tested.
- Don’t: Overuse manual memory management. Leverage STL containers for safe and efficient handling.
STL Algorithms
STL algorithms perform operations on collections, such as sorting and searching.
Get Life is a SoC’s stories in your inbox
Join Medium for free to get updates from this writer.
Example Code: STLAlgorithms.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {3, 1, 4, 1, 5, 9};
std::sort(numbers.begin(), numbers.end());
std::cout << "Sorted numbers: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
if (std::binary_search(numbers.begin(), numbers.end(), 4)) {
std::cout << "4 found in the vector." << std::endl;
}
return 0;
}Explanation
- std::sort: Sorts elements in ascending order.
- std::binary_search: Searches for an element in a sorted collection.
Compiler-Level Insights
- Algorithm Complexity: Understand the complexity of STL algorithms to choose the right one for your needs.
- Algorithm Customization: Use custom comparators and predicates for specialized behavior.
Common Errors and Solutions
- Predicate Errors: Ensure predicates used with algorithms are valid and consistent.
- Algorithm Compatibility: Use algorithms compatible with the iterator types of your containers.
Do’s and Don’ts
- Do: Use STL algorithms for common operations like sorting and searching.
- Don’t: Rewrite standard algorithms unless necessary. They are well-optimized.
Iterators
Iterators provide a uniform interface for traversing collections, supporting STL algorithms.
Example Code: Iterators.
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {10, 20, 30, 40};
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}Explanation
- Iterators: Abstract pointers that facilitate traversal and manipulation of collections.
Compiler-Level Insights
- Iterator Categories: Understand iterator categories (input, output, forward, bidirectional, random access) for efficient use.
- Iterator Traits: Use iterator traits to write generic code that works with various iterator types.
Common Errors and Solutions
- Invalid Iterators: Avoid using invalidated iterators, especially after modifying containers.
- Iterator Type Mismatch: Ensure iterator types match the container type.
Do’s and Don’ts
- Do: Use iterators for container traversal to ensure compatibility with STL algorithms.
- Don’t: Assume iterator validity. Always check for out-of-bounds errors.
Project 1: Use Templates and STL in the Command-Line Tool
Objective
Implement a feature to sort and display a list of users using STL containers and algorithms.
Code: UserManager.h
#ifndef USERMANAGER_H
#define USERMANAGER_H
#include <iostream>
#include <vector>
#include <algorithm>
#include "User.h"
class UserManager {
private:
std::vector<User> users;
public:
void addUser(const std::string& name, int age) {
users.emplace_back(name, age);
}
void displayUsers() const {
for (const auto& user : users) {
user.display();
}
}
void sortUsersByAge() {
std::sort(users.begin(), users.end(), [](const User& a, const User& b) {
return a.getAge() < b.getAge();
});
}
};
#endifCode: main.
#include <iostream>
#include "UserManager.h"
int main() {
UserManager manager;
manager.addUser("Alice", 30);
manager.addUser("Bob", 25);
manager.addUser("Charlie", 35);
std::cout << "Users before sorting:" << std::endl;
manager.displayUsers();
manager.sortUsersByAge();
std::cout << "Users after sorting by age:" << std::endl;
manager.displayUsers();
return 0;
}Explanation
- STL Containers and Algorithms: std::vector stores User objects, and std::sort orders them by age.
- Lambda Expressions: Concise syntax for defining inline functions.
Common Scenarios and Solutions
- Algorithm Complexity: Choose algorithms based on complexity for performance.
- Predicate Validation: Ensure predicates are consistent and logical for sorting.
Do’s and Don’ts
- Do: Use lambdas for short, throwaway functions. They improve readability.
- Don’t: Overuse complex lambdas. Extract logic to named functions for clarity.
Project 2: Implement Templates and STL in Task Management
Objective
Use templates and STL to manage tasks efficiently.
Code: TaskManager.h
#ifndef TASKMANAGER_H
#define TASKMANAGER_H
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include "Task.h"
class TaskManager {
private:
std::vector<std::unique_ptr<Task>> tasks;
public:
template <typename T, typename… Args>
void addTask(Args&&… args) {
tasks.push_back(std::make_unique<T>(std::forward<Args>(args)…));
}
void displayTasks() const {
for (const auto& task : tasks) {
task->display();
}
}
void sortTasksByPriority() {
std::sort(tasks.begin(), tasks.end(), [](const std::unique_ptr<Task>& a, const std::unique_ptr<Task>& b) {
return a->getPriority() < b->getPriority();
});
}
};
#endifCode: main.
#include <iostream>
#include "TaskManager.h"
int main() {
TaskManager manager;
manager.addTask<BasicTask>("Finish report", "2023–08–01", 1);
manager.addTask<RecurringTask>("Weekly meeting", "Every Monday");
std::cout << "Tasks before sorting:" << std::endl;
manager.displayTasks();
manager.sortTasksByPriority();
std::cout << "Tasks after sorting by priority:" << std::endl;
manager.displayTasks();
return 0;
}Explanation
- Template Functions: Enable generic task creation with flexible arguments.
- STL Algorithms: Sort tasks by priority using std::sort.
Common Scenarios and Solutions
- Template Argument Deduction: Use std::forward for perfect forwarding in template functions.
- Container Operations: Ensure operations maintain iterator validity.
Conclusion
This article explored advanced C++ features like templates and the Standard Template Library (STL), enhancing the flexibility and efficiency of our code. These tools allow us to write generic, reusable code and perform complex operations on data structures. In the next article, we’ll delve into concurrency and multithreading, enabling parallel execution of tasks.






