Modern C++ Features (C++11 and Later)
Modern C++ (C++11 and later standards) introduced many powerful features that make C++ code more expressive, safer, and easier to write. This guide covers the most important modern C++ features that every developer should know.
Auto Keyword
The auto keyword allows the compiler to automatically deduce the type of a variable from its initializer. This reduces code verbosity and makes it easier to maintain.
Basic Auto Usage
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
// Basic type deduction
auto x = 42; // x is int
auto y = 3.14; // y is double
auto z = 'a'; // z is char
auto name = "Hello"; // name is const char*
// With STL containers
auto numbers = vector<int>{1, 2, 3, 4, 5};
auto text = string("Modern C++");
// Auto with function return types
auto result = sqrt(16.0); // result is double
cout << "x: " << x << " (type: int)" << endl;
cout << "y: " << y << " (type: double)" << endl;
cout << "text: " << text << endl;
return 0;
}
Auto with Complex Types
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
// Auto with complex types
auto grades = map<string, int>{
{"Alice", 95},
{"Bob", 87},
{"Charlie", 92}
};
// Much cleaner than: map<string, int>::iterator it = grades.begin();
for (auto it = grades.begin(); it != grades.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
return 0;
}
Lambda Expressions
Lambda expressions allow you to define anonymous functions inline. They are particularly useful for short functions used with STL algorithms.
Lambda Syntax
[capture](parameters) -> return_type { body }
Basic Lambda Examples
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// Simple lambda with no parameters
auto greet = []() {
cout << "Hello from lambda!" << endl;
};
greet();
// Lambda with parameters
auto add = [](int a, int b) {
return a + b;
};
cout << "Sum: " << add(5, 3) << endl;
// Lambda with capture by value
int multiplier = 10;
auto multiply = [multiplier](int x) {
return x * multiplier;
};
cout << "Result: " << multiply(5) << endl;
// Lambda with capture by reference
int counter = 0;
auto increment = [&counter]() {
counter++;
cout << "Counter: " << counter << endl;
};
increment();
increment();
return 0;
}
Lambda with STL Algorithms
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Find even numbers
auto even_count = count_if(numbers.begin(), numbers.end(), [](int n) {
return n % 2 == 0;
});
cout << "Even numbers: " << even_count << endl;
// Transform each element
vector<int> doubled;
transform(numbers.begin(), numbers.end(), back_inserter(doubled), [](int n) {
return n * 2;
});
cout << "Doubled: ";
for (const auto& num : doubled) {
cout << num << " ";
}
cout << endl;
return 0;
}
Range-Based For Loops
Range-based for loops provide a clean and concise way to iterate over containers and arrays.
Basic Range-Based For Loops
#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main() {
// With vectors
vector<int> numbers = {1, 2, 3, 4, 5};
cout << "Numbers: ";
for (const auto& num : numbers) {
cout << num << " ";
}
cout << endl;
// With strings
string text = "Hello";
cout << "Characters: ";
for (const auto& ch : text) {
cout << ch << " ";
}
cout << endl;
// With maps
map<string, int> scores = {
{"Alice", 95},
{"Bob", 87},
{"Charlie", 92}
};
cout << "Scores:" << endl;
for (const auto& [name, score] : scores) { // C++17 structured bindings
cout << name << ": " << score << endl;
}
// Modifying elements
vector<int> data = {1, 2, 3, 4, 5};
for (auto& value : data) {
value *= 2; // Double each value
}
cout << "Modified data: ";
for (const auto& value : data) {
cout << value << " ";
}
cout << endl;
return 0;
}
Range-Based For with Arrays
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
cout << "Array elements: ";
for (const auto& element : arr) {
cout << element << " ";
}
cout << endl;
return 0;
}
Smart Pointers Basics
Smart pointers automatically manage memory and help prevent memory leaks and dangling pointers. They are a cornerstone of modern C++ memory management.
std::unique_ptr
std::unique_ptr represents exclusive ownership of a resource. It cannot be copied but can be moved.
#include <iostream>
#include <memory>
using namespace std;
class Person {
public:
string name;
int age;
Person(const string& n, int a) : name(n), age(a) {
cout << "Person " << name << " created" << endl;
}
~Person() {
cout << "Person " << name << " destroyed" << endl;
}
void introduce() {
cout << "Hi, I'm " << name << ", " << age << " years old" << endl;
}
};
int main() {
// Creating unique_ptr
auto person1 = make_unique<Person>("Alice", 25);
person1->introduce();
// Moving ownership
auto person2 = move(person1);
// person1 is now null
if (!person1) {
cout << "person1 is null after move" << endl;
}
person2->introduce();
// Automatic cleanup when person2 goes out of scope
return 0;
}
std::shared_ptr
std::shared_ptr allows multiple pointers to share ownership of the same resource. The resource is automatically deleted when the last shared_ptr is destroyed.
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Resource {
public:
string data;
Resource(const string& d) : data(d) {
cout << "Resource '" << data << "' created" << endl;
}
~Resource() {
cout << "Resource '" << data << "' destroyed" << endl;
}
};
int main() {
// Creating shared_ptr
auto resource1 = make_shared<Resource>("Important Data");
cout << "Reference count: " << resource1.use_count() << endl;
// Sharing ownership
auto resource2 = resource1;
cout << "Reference count after copy: " << resource1.use_count() << endl;
// Using in containers
vector<shared_ptr<Resource>> resources;
resources.push_back(resource1);
resources.push_back(resource2);
cout << "Reference count after adding to vector: " << resource1.use_count() << endl;
// Reset one pointer
resource2.reset();
cout << "Reference count after reset: " << resource1.use_count() << endl;
// Resource will be automatically cleaned up when all shared_ptrs are destroyed
return 0;
}
Smart Pointer Best Practices
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
// Factory function returning unique_ptr
unique_ptr<int> createNumber(int value) {
return make_unique<int>(value);
}
// Function accepting unique_ptr by reference (doesn't transfer ownership)
void processNumber(const unique_ptr<int>& num) {
cout << "Processing number: " << *num << endl;
}
// Function accepting shared_ptr
void shareNumber(shared_ptr<int> num) {
cout << "Sharing number: " << *num << endl;
cout << "Reference count in function: " << num.use_count() << endl;
}
int main() {
// Use make_unique and make_shared
auto num1 = make_unique<int>(42);
auto num2 = make_shared<int>(100);
// Process without transferring ownership
processNumber(num1);
// Share ownership
shareNumber(num2);
cout << "Reference count after function: " << num2.use_count() << endl;
// Use smart pointers in containers
vector<unique_ptr<int>> numbers;
numbers.push_back(make_unique<int>(1));
numbers.push_back(make_unique<int>(2));
numbers.push_back(make_unique<int>(3));
cout << "Numbers in vector: ";
for (const auto& num : numbers) {
cout << *num << " ";
}
cout << endl;
return 0;
}
Combining Modern Features
Here's an example that combines multiple modern C++ features:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
using namespace std;
class Task {
public:
string description;
int priority;
Task(const string& desc, int prio) : description(desc), priority(prio) {}
void execute() const {
cout << "Executing: " << description << " (Priority: " << priority << ")" << endl;
}
};
int main() {
// Create tasks using modern features
auto tasks = vector<shared_ptr<Task>>{
make_shared<Task>("Write code", 1),
make_shared<Task>("Review code", 2),
make_shared<Task>("Test application", 1),
make_shared<Task>("Deploy to production", 3)
};
// Sort by priority using lambda
sort(tasks.begin(), tasks.end(), [](const auto& a, const auto& b) {
return a->priority < b->priority;
});
// Execute high priority tasks (priority 1 and 2) using modern loops and lambdas
cout << "Executing high priority tasks:" << endl;
for (const auto& task : tasks) {
if (task->priority <= 2) {
task->execute();
}
}
// Count tasks by priority using lambda
auto high_priority_count = count_if(tasks.begin(), tasks.end(), [](const auto& task) {
return task->priority == 1;
});
cout << "High priority tasks: " << high_priority_count << endl;
return 0;
}
Summary
Modern C++ features make code:
- More expressive:
auto, lambdas, and range-based for loops - Safer: Smart pointers prevent memory leaks
- More maintainable: Type deduction reduces code duplication
- More efficient: Move semantics and smart pointers improve performance
These features work together to create cleaner, safer, and more maintainable C++ code. Start incorporating them into your projects to write modern, idiomatic C++!