Giao diện
⚡ STL Algorithms & Lambdas: Sức Mạnh Kết Hợp Module 3
Ngừng viết raw loop. STL algorithms + lambdas là combo mạnh nhất trong C++ hiện đại — code ngắn hơn, rõ ý hơn, và thường nhanh hơn cả vòng lặp bạn tự viết. Bài này biến bạn thành người viết code C++ thực sự biểu cảm.
🎯 Mục tiêu
Sau bài này, bạn sẽ:
- Nắm vững các STL algorithms quan trọng nhất:
sort,find,count,transform,for_each,accumulate - Hiểu cú pháp lambda và capture semantics — khi nào dùng
[=],[&],[this] - Viết code C++ ngắn gọn, biểu cảm, thay thế hoàn toàn raw
forloop - Biết idiom Erase-Remove và preview C++20 Ranges
- Áp dụng algorithms + lambdas vào bài toán thực tế (Order Analytics)
🧠 1. Triết Lý: "Đừng Tự Viết Loop"
Trước khi đi vào cú pháp, hãy hiểu tại sao STL algorithms tồn tại.
Raw Loop vs. Algorithm — Không chỉ là style
cpp
// ❌ Raw loop — ý định không rõ ràng
for (int i = 0; i < v.size(); ++i) {
if (v[i] == target) {
// tìm thấy...
break;
}
}
// ✅ Algorithm — ý định crystal clear
auto it = std::find(v.begin(), v.end(), target);📌 Nguyên tắc vàng
Khi bạn viết std::find, bất kỳ C++ developer nào đọc code đều biết ngay: "Đang tìm một phần tử." Raw for loop không nói gì về ý định — người đọc phải đọc toàn bộ loop body mới hiểu bạn đang làm gì.
Tại sao STL Algorithms thắng?
| Tiêu chí | Raw Loop | STL Algorithm |
|---|---|---|
| Ý định | Phải đọc body mới hiểu | Tên algorithm = ý định |
| Tốc độ | Thường chậm hơn | Tối ưu bởi compiler + lib authors |
| Bug | Dễ sai index, off-by-one | Battle-tested, hàng triệu người dùng |
| Bảo trì | Khó refactor | Dễ thay đổi logic (đổi lambda) |
| Parallelism | Phải viết lại từ đầu | Thêm execution policy (C++17) |
🎓 Bạn có biết?
std::sort sử dụng introsort — kết hợp quicksort + heapsort + insertion sort. Nó tự động chọn chiến lược tốt nhất tùy thuộc vào dữ liệu. Bạn gần như không bao giờ viết được sort nhanh hơn std::sort.
🔧 2. Lambda Expressions — Cú Pháp Cốt Lõi
Lambda là anonymous function — hàm không tên, định nghĩa ngay tại chỗ sử dụng.
Cú pháp tổng quát
cpp
[capture](parameters) -> return_type { body }Trong đó:
[capture]— biến nào từ scope ngoài được dùng bên trong lambda(parameters)— tham số giống function bình thường-> return_type— kiểu trả về (thường bỏ qua, compiler tự suy){ body }— thân hàm
Ví dụ cơ bản
cpp
// Lambda đơn giản — tính bình phương
auto square = [](int x) { return x * x; };
std::cout << square(5); // 25
// Lambda với string_view — chào hỏi
auto greet = [](std::string_view name) {
std::cout << "Xin chào, " << name << "!\n";
};
greet("Penalgo"); // Xin chào, Penalgo!
// Lambda không tham số
auto dice = []() { return rand() % 6 + 1; };Lambda với auto parameter (C++14+)
cpp
// Generic lambda — hoạt động với mọi kiểu
auto print = [](const auto& value) {
std::cout << value << "\n";
};
print(42); // int
print(3.14); // double
print("Penalgo"s); // string🔑 Mẹo nhớ
Lambda = hàm nhỏ dùng một lần, thường truyền vào algorithm. Nếu lambda dài hơn 5-6 dòng, hãy cân nhắc viết thành named function.
🎯 3. Capture Semantics — Trọng Tâm Của Lambda
Capture quyết định lambda truy cập được biến nào từ scope bên ngoài.
Bảng capture modes
| Cú pháp | Ý nghĩa | Khi nào dùng |
|---|---|---|
[] | Không capture gì | Lambda độc lập, chỉ dùng parameters |
[x] | Capture x by value (copy) | Cần giá trị tại thời điểm tạo lambda |
[&x] | Capture x by reference | Cần thay đổi x hoặc tránh copy đắt |
[=] | Capture tất cả by value | Tiện nhưng nên tránh — không rõ ràng |
[&] | Capture tất cả by reference | Nguy hiểm nếu lambda outlive scope |
[=, &x] | Tất cả by value, riêng x by reference | Cần đặc biệt thay đổi x |
[this] | Capture con trỏ this | Dùng trong member function |
Ví dụ từng mode
cpp
int threshold = 100;
std::string label = "Expensive";
// [x] — capture by value
auto check_v = [threshold](int price) {
return price > threshold;
// threshold là BẢN SAO — thay đổi threshold bên ngoài không ảnh hưởng
};
// [&x] — capture by reference
auto check_r = [&threshold](int price) {
threshold += 10; // THAY ĐỔI biến gốc!
return price > threshold;
};
// [=] — capture all by value
auto describe = [=](int price) {
// Dùng được cả threshold và label (bản sao)
if (price > threshold) {
std::cout << label << ": " << price << "\n";
}
};
// [&] — capture all by reference
auto adjust = [&](int delta) {
threshold += delta; // Thay đổi threshold gốc
label = "Adjusted"; // Thay đổi label gốc
};
// [=, &x] — mix mode
auto mixed = [=, &threshold](int price) {
threshold = price; // Thay đổi threshold gốc
// label là bản sao — không đổi label gốc
std::cout << label << ": " << threshold << "\n";
};Capture this trong class
cpp
class OrderProcessor {
double tax_rate_ = 0.1;
void process(std::vector<double>& prices) {
// [this] — truy cập member variables
std::transform(prices.begin(), prices.end(), prices.begin(),
[this](double price) {
return price * (1 + tax_rate_);
});
}
};⚠️ Capture by Reference — Mối Nguy Hiểm Ẩn
cpp
// ❌ NGUY HIỂM — Dangling Reference!
std::function<int()> create_counter() {
int count = 0;
return [&count]() { return ++count; };
// count bị hủy khi hàm return
// lambda vẫn giữ reference → UNDEFINED BEHAVIOR
}
// ✅ AN TOÀN — Capture by value
std::function<int()> create_counter() {
int count = 0;
return [count]() mutable { return ++count; };
// count là bản sao — sống cùng lambda
}Quy tắc: Nếu lambda outlive scope tạo ra nó (return, lưu vào biến, truyền sang thread khác), PHẢI capture by value.
🗡️ 4. Core Algorithms Tour
Đây là những algorithm bạn sẽ dùng hàng ngày. Tất cả nằm trong <algorithm> (trừ accumulate ở <numeric>).
4.1 std::sort — Sắp xếp
cpp
#include <algorithm>
#include <vector>
std::vector<int> nums = {5, 2, 8, 1, 9, 3};
// Sắp xếp tăng dần (mặc định)
std::sort(nums.begin(), nums.end());
// nums = {1, 2, 3, 5, 8, 9}
// Sắp xếp giảm dần
std::sort(nums.begin(), nums.end(), std::greater<>{});
// nums = {9, 8, 5, 3, 2, 1}Custom comparator với lambda — ví dụ thực tế:
cpp
struct Order {
std::string customer;
double amount;
std::string date; // "2024-01-15"
};
std::vector<Order> orders = { /* ... */ };
// Sắp xếp theo amount giảm dần
std::sort(orders.begin(), orders.end(),
[](const Order& a, const Order& b) {
return a.amount > b.amount;
});
// Sắp xếp theo date, nếu trùng thì theo amount giảm
std::sort(orders.begin(), orders.end(),
[](const Order& a, const Order& b) {
if (a.date != b.date) return a.date < b.date;
return a.amount > b.amount;
});🎓 std::stable_sort
Nếu cần giữ thứ tự tương đối của các phần tử bằng nhau, dùng std::stable_sort. Nó chậm hơn một chút (O(n log²n) worst case) nhưng ổn định — quan trọng khi sort theo nhiều tiêu chí.
4.2 std::find / std::find_if — Tìm kiếm
cpp
std::vector<int> nums = {10, 20, 30, 42, 50};
// Tìm giá trị cụ thể
auto it = std::find(nums.begin(), nums.end(), 42);
if (it != nums.end()) {
std::cout << "Tìm thấy " << *it << " tại index "
<< std::distance(nums.begin(), it) << "\n";
}
// Tìm theo điều kiện — order đầu tiên > 1 triệu
auto expensive = std::find_if(orders.begin(), orders.end(),
[](const Order& o) { return o.amount > 1'000'000; });
if (expensive != orders.end()) {
std::cout << "Đơn hàng lớn: " << expensive->customer
<< " - " << expensive->amount << "\n";
}
// Tìm phần tử KHÔNG thỏa điều kiện
auto non_premium = std::find_if_not(orders.begin(), orders.end(),
[](const Order& o) { return o.amount > 500'000; });4.3 std::count / std::count_if — Đếm
cpp
std::vector<int> scores = {85, 92, 67, 45, 91, 78, 95, 55};
// Đếm số điểm A (>= 90)
int grade_a = std::count_if(scores.begin(), scores.end(),
[](int score) { return score >= 90; });
std::cout << "Số học sinh đạt A: " << grade_a << "\n"; // 3
// Đếm chính xác giá trị
int perfect = std::count(scores.begin(), scores.end(), 100);
// Đếm đơn hàng vượt ngưỡng — capture threshold
double min_amount = 500'000;
int big_orders = std::count_if(orders.begin(), orders.end(),
[min_amount](const Order& o) {
return o.amount >= min_amount;
});4.4 std::transform — Biến đổi
transform tạo output mới từ input — giống map() trong các ngôn ngữ khác.
cpp
std::vector<double> prices = {100.0, 250.0, 80.0, 420.0};
// Tính giá sau thuế 10%
std::vector<double> with_tax;
std::transform(prices.begin(), prices.end(),
std::back_inserter(with_tax),
[](double p) { return p * 1.1; });
// with_tax = {110.0, 275.0, 88.0, 462.0}
// Transform in-place — viết hoa tất cả ký tự
std::string name = "penalgo";
std::transform(name.begin(), name.end(), name.begin(),
[](char c) { return std::toupper(c); });
// name = "PENALGO"
// Transform 2 container — tính tổng từng cặp
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {10, 20, 30};
std::vector<int> sums;
std::transform(a.begin(), a.end(), b.begin(),
std::back_inserter(sums),
[](int x, int y) { return x + y; });
// sums = {11, 22, 33}4.5 std::accumulate — Tổng hợp
Nằm trong <numeric>, không phải <algorithm>.
cpp
#include <numeric>
std::vector<double> prices = {100.0, 250.0, 80.0, 420.0};
// Tính tổng
double total = std::accumulate(prices.begin(), prices.end(), 0.0);
// total = 850.0
// ⚠️ Cẩn thận kiểu init value!
// int total = std::accumulate(prices.begin(), prices.end(), 0);
// → Kết quả SAI vì 0 là int, tất cả bị truncate!
// Custom operation — tính tích
double product = std::accumulate(prices.begin(), prices.end(), 1.0,
[](double acc, double price) { return acc * price; });
// Tính tổng amount từ vector<Order>
double total_revenue = std::accumulate(orders.begin(), orders.end(), 0.0,
[](double sum, const Order& o) { return sum + o.amount; });⚠️ Bẫy Kiểu Init Value
cpp
std::vector<double> vals = {1.5, 2.5, 3.5};
// ❌ Init value = 0 (int) → kết quả bị truncate thành int
auto bad = std::accumulate(vals.begin(), vals.end(), 0); // = 6 (int!)
// ✅ Init value = 0.0 (double) → kết quả đúng
auto good = std::accumulate(vals.begin(), vals.end(), 0.0); // = 7.5Kiểu của init value quyết định kiểu của kết quả. Luôn dùng 0.0 cho double, 0L cho long.
4.6 std::for_each — Duyệt và thực thi
cpp
std::vector<std::string> names = {"An", "Bình", "Cường", "Dũng"};
// In từng phần tử
std::for_each(names.begin(), names.end(),
[](const std::string& name) {
std::cout << "Xin chào " << name << "!\n";
});
// Thay đổi in-place (capture by reference không cần — parameter là reference)
std::vector<int> nums = {1, 2, 3, 4, 5};
std::for_each(nums.begin(), nums.end(),
[](int& n) { n *= 2; });
// nums = {2, 4, 6, 8, 10}4.7 std::all_of / std::any_of / std::none_of — Kiểm tra điều kiện
cpp
std::vector<int> scores = {85, 92, 78, 91, 88};
// Tất cả đều >= 70?
bool all_pass = std::all_of(scores.begin(), scores.end(),
[](int s) { return s >= 70; });
// true
// Có ai đạt 100 không?
bool has_perfect = std::any_of(scores.begin(), scores.end(),
[](int s) { return s == 100; });
// false
// Không ai dưới 50?
bool no_fail = std::none_of(scores.begin(), scores.end(),
[](int s) { return s < 50; });
// true📊 5. Bảng Tra Cứu Algorithm
| Nhiệm vụ | Algorithm | Complexity | Header |
|---|---|---|---|
| Sắp xếp | std::sort | O(n log n) | <algorithm> |
| Sắp xếp ổn định | std::stable_sort | O(n log²n) | <algorithm> |
| Tìm phần tử | std::find | O(n) | <algorithm> |
| Tìm theo điều kiện | std::find_if | O(n) | <algorithm> |
| Đếm | std::count_if | O(n) | <algorithm> |
| Biến đổi | std::transform | O(n) | <algorithm> |
| Tổng hợp | std::accumulate | O(n) | <numeric> |
| Kiểm tra tất cả | std::all_of | O(n) | <algorithm> |
| Kiểm tra bất kỳ | std::any_of | O(n) | <algorithm> |
| Kiểm tra không có | std::none_of | O(n) | <algorithm> |
| Xóa theo điều kiện | std::remove_if + erase | O(n) | <algorithm> |
| Giá trị lớn nhất | std::max_element | O(n) | <algorithm> |
| Sao chép có điều kiện | std::copy_if | O(n) | <algorithm> |
🔄 6. Erase-Remove Idiom — Xóa Phần Tử Đúng Cách
Vấn đề: std::remove không thực sự xóa!
std::remove chỉ dồn các phần tử không bị xóa lên đầu, trả về iterator đến "vùng rác". Bạn phải gọi erase để thực sự xóa.
cpp
std::vector<int> v = {1, 0, 3, 0, 5, 0, 7};
// Bước 1: remove dồn phần tử — v trở thành {1, 3, 5, 7, ?, ?, ?}
auto new_end = std::remove(v.begin(), v.end(), 0);
// Bước 2: erase vùng rác
v.erase(new_end, v.end());
// v = {1, 3, 5, 7}
// Viết gọn trong 1 dòng — Erase-Remove Idiom
v.erase(std::remove(v.begin(), v.end(), 0), v.end());Xóa theo điều kiện
cpp
// Xóa tất cả số âm
v.erase(
std::remove_if(v.begin(), v.end(),
[](int x) { return x < 0; }),
v.end()
);C++20: Đơn giản hơn nhiều!
cpp
// C++20 — std::erase / std::erase_if (trong <vector>)
std::vector<int> v = {1, 0, -3, 0, 5, -2, 7};
std::erase(v, 0); // Xóa tất cả số 0
// v = {1, -3, 5, -2, 7}
std::erase_if(v, [](int x) { return x < 0; }); // Xóa số âm
// v = {1, 5, 7}📌 C++20 std::erase_if
Nếu compiler hỗ trợ C++20, luôn dùng std::erase / std::erase_if. Ngắn gọn hơn, an toàn hơn, không cần nhớ idiom phức tạp.
🚀 7. C++20 Ranges — Tương Lai Của Algorithms
C++20 Ranges cho phép chain nhiều operations lại bằng pipe |, giống style functional programming.
cpp
#include <ranges>
#include <vector>
#include <string>
struct Order {
std::string customer;
double amount;
std::string receipt() const {
return customer + ": " + std::to_string(amount);
}
};
std::vector<Order> orders = { /* ... */ };
// Pipeline: lọc → biến đổi → lấy kết quả
auto expensive_receipts = orders
| std::views::filter([](const Order& o) {
return o.amount > 1'000'000;
})
| std::views::transform([](const Order& o) {
return o.receipt();
});
// Lazy evaluation — chỉ tính khi duyệt
for (const auto& receipt : expensive_receipts) {
std::cout << receipt << "\n";
}So sánh: Traditional vs. Ranges
cpp
// ❌ Traditional — nhiều bước, nhiều container tạm
std::vector<Order> filtered;
std::copy_if(orders.begin(), orders.end(),
std::back_inserter(filtered),
[](const Order& o) { return o.amount > 1'000'000; });
std::vector<std::string> receipts;
std::transform(filtered.begin(), filtered.end(),
std::back_inserter(receipts),
[](const Order& o) { return o.receipt(); });
// ✅ Ranges — một pipeline, không container tạm
auto receipts = orders
| std::views::filter([](const Order& o) { return o.amount > 1'000'000; })
| std::views::transform([](const Order& o) { return o.receipt(); });🎓 Ranges = Tương lai
Ranges là hướng đi của C++ algorithms. Nếu compiler hỗ trợ C++20, hãy bắt đầu dùng std::views cho code mới. Pipeline style rõ ràng, ít bug, và lazy (không tạo container trung gian).
💼 8. Ví Dụ Thực Tế: Order Analytics
Kết hợp mọi thứ đã học vào một bài toán business thực tế.
cpp
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
struct Order {
std::string customer;
double amount;
std::string date; // "2024-01-15"
std::string category; // "electronics", "food", "clothing"
std::string receipt() const {
return "[" + date + "] " + customer + ": "
+ std::to_string(amount) + " VND";
}
};
void analyze_orders(std::vector<Order> orders) {
// 1. Tổng doanh thu
double total_revenue = std::accumulate(
orders.begin(), orders.end(), 0.0,
[](double sum, const Order& o) { return sum + o.amount; });
std::cout << "Tổng doanh thu: " << std::fixed << std::setprecision(0)
<< total_revenue << " VND\n";
// 2. Đếm đơn hàng lớn (> 5 triệu)
constexpr double BIG_ORDER_THRESHOLD = 5'000'000;
int big_count = std::count_if(
orders.begin(), orders.end(),
[](const Order& o) { return o.amount > BIG_ORDER_THRESHOLD; });
std::cout << "Đơn hàng lớn (>5M): " << big_count << "\n";
// 3. Sắp xếp theo amount giảm dần
std::sort(orders.begin(), orders.end(),
[](const Order& a, const Order& b) {
return a.amount > b.amount;
});
// 4. Top 3 đơn hàng lớn nhất
std::cout << "\n--- Top 3 đơn hàng ---\n";
int top_n = std::min(3, static_cast<int>(orders.size()));
std::for_each(orders.begin(), orders.begin() + top_n,
[](const Order& o) {
std::cout << o.receipt() << "\n";
});
// 5. Tạo danh sách receipt cho đơn hàng electronics
std::vector<std::string> elec_receipts;
// Lọc + biến đổi — dùng copy_if sẽ không biến đổi được
// → Kết hợp for_each với điều kiện, hoặc dùng ranges
for (const auto& o : orders) {
if (o.category == "electronics") {
elec_receipts.push_back(o.receipt());
}
}
// Hoặc C++20 Ranges:
// auto elec_receipts = orders
// | std::views::filter([](const Order& o) {
// return o.category == "electronics"; })
// | std::views::transform(&Order::receipt);
// 6. Kiểm tra: tất cả đơn hàng > 0?
bool all_valid = std::all_of(
orders.begin(), orders.end(),
[](const Order& o) { return o.amount > 0; });
std::cout << "\nTất cả đơn hàng hợp lệ: "
<< (all_valid ? "✅ Có" : "❌ Không") << "\n";
// 7. Xóa đơn hàng bị hủy (amount = 0) — Erase-Remove
orders.erase(
std::remove_if(orders.begin(), orders.end(),
[](const Order& o) { return o.amount <= 0; }),
orders.end());
std::cout << "Sau khi xóa đơn hủy: " << orders.size() << " đơn\n";
}💼 Production Story
Một hệ thống e-commerce xử lý 50,000 đơn hàng/giây đã chuyển từ raw loops sang STL algorithms + lambdas. Kết quả: code giảm 40% số dòng, bug rate giảm 60%, và performance cải thiện 15% nhờ compiler tối ưu tốt hơn cho standard algorithms.
✏️ 9. Fast Exercise: Thay Thế Raw Loop
✏️ Bài tập nhanh
Hãy viết lại các raw loop sau bằng STL algorithm + lambda phù hợp.
Bài 1: Tìm phần tử đầu tiên > 100
cpp
// ❌ Raw loop — hãy thay bằng std::find_if
int result = -1;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] > 100) {
result = nums[i];
break;
}
}💡 Xem đáp án
cpp
auto it = std::find_if(nums.begin(), nums.end(),
[](int n) { return n > 100; });
int result = (it != nums.end()) ? *it : -1;Bài 2: Tính tổng tất cả số chẵn
cpp
// ❌ Raw loop — hãy dùng kết hợp algorithms
int sum = 0;
for (int n : nums) {
if (n % 2 == 0) {
sum += n;
}
}💡 Xem đáp án
cpp
// Cách 1: accumulate với điều kiện trong lambda
int sum = std::accumulate(nums.begin(), nums.end(), 0,
[](int acc, int n) { return n % 2 == 0 ? acc + n : acc; });
// Cách 2 (C++20 Ranges): filter rồi accumulate
// auto evens = nums | std::views::filter([](int n) { return n % 2 == 0; });
// int sum = std::accumulate(evens.begin(), evens.end(), 0);Bài 3: Chuyển vector<string> thành vector<int> chứa độ dài
cpp
// ❌ Raw loop — hãy dùng std::transform
std::vector<std::string> words = {"Hello", "World", "C++"};
std::vector<int> lengths;
for (const auto& w : words) {
lengths.push_back(w.size());
}💡 Xem đáp án
cpp
std::vector<int> lengths;
std::transform(words.begin(), words.end(),
std::back_inserter(lengths),
[](const std::string& w) { return static_cast<int>(w.size()); });
// lengths = {5, 5, 3}🐛 10. Spot The Bug!
🐛 Bug Hunt Challenge
Tìm bug trong đoạn code sau. Mỗi đoạn có ít nhất 1 lỗi.
Bug #1: Lambda capture
cpp
std::vector<std::function<int()>> counters;
for (int i = 0; i < 5; ++i) {
counters.push_back([&i]() { return i; });
}
// Gọi counters[0](), counters[1]()... sẽ cho kết quả gì?🔍 Phân tích bug
Bug: Capture i by reference [&i]. Sau khi vòng for kết thúc, i = 5 (hoặc undefined behavior vì i ra khỏi scope). Tất cả lambda đều trả về cùng một giá trị!
Fix: Capture by value [i]:
cpp
counters.push_back([i]() { return i; });
// Mỗi lambda giữ bản sao riêng: 0, 1, 2, 3, 4Bug #2: Accumulate init value
cpp
std::vector<double> prices = {19.99, 29.99, 9.99};
auto total = std::accumulate(prices.begin(), prices.end(), 0);
std::cout << "Total: " << total << "\n"; // Mong đợi: 59.97🔍 Phân tích bug
Bug: Init value 0 là int, nên accumulate trả về int. Mỗi lần cộng, kết quả bị truncate: 0 + 19.99 → 19, 19 + 29.99 → 48, 48 + 9.99 → 57.
Output thực tế: Total: 57 (không phải 59.97!)
Fix: Dùng 0.0:
cpp
auto total = std::accumulate(prices.begin(), prices.end(), 0.0);
// total = 59.97 ✅Bug #3: Remove without erase
cpp
std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
std::remove(v.begin(), v.end(), 2);
std::cout << "Size: " << v.size() << "\n"; // Mong đợi: 4🔍 Phân tích bug
Bug: std::remove không thay đổi size của container! Nó chỉ dồn phần tử lên đầu. v.size() vẫn là 7.
Fix: Dùng Erase-Remove Idiom:
cpp
v.erase(std::remove(v.begin(), v.end(), 2), v.end());
std::cout << "Size: " << v.size() << "\n"; // 4 ✅
// Hoặc C++20:
// std::erase(v, 2);⚡ 11. Performance Notes
⚡ Hiệu năng cần biết
std::sort — Introsort
std::sort sử dụng introsort: bắt đầu với quicksort, chuyển sang heapsort nếu recursion quá sâu, và dùng insertion sort cho partition nhỏ. Kết quả:
- Average: O(n log n)
- Worst case: O(n log n) — không bị O(n²) như naive quicksort
- Cache-friendly: Insertion sort cho partition nhỏ tận dụng cache tốt
Đừng tự viết sort. std::sort gần như luôn nhanh hơn.
Execution Policies (C++17)
cpp
#include <execution>
// Parallel sort — tận dụng multi-core
std::sort(std::execution::par, data.begin(), data.end());
// Parallel + vectorized
std::sort(std::execution::par_unseq, data.begin(), data.end());Chỉ cần thêm một tham số, code của bạn tự động chạy song song. Đây là lý do dùng STL algorithms thay vì raw loops.
🚫 12. Anti-Patterns — Những Điều KHÔNG Nên Làm
☠️ Production Anti-Patterns
Những lỗi này xuất hiện trong code review hàng ngày. Tránh bằng mọi giá.
Anti-Pattern 1: Raw loop thay vì algorithm
cpp
// ❌ Viết loop tìm kiếm — không ai biết bạn đang tìm gì cho đến khi đọc xong
bool found = false;
for (size_t i = 0; i < users.size(); ++i) {
if (users[i].id == target_id) {
found = true;
break;
}
}
// ✅ Algorithm — ý định rõ ràng từ dòng đầu
auto it = std::find_if(users.begin(), users.end(),
[target_id](const User& u) { return u.id == target_id; });
bool found = (it != users.end());Anti-Pattern 2: Lambda quá phức tạp
cpp
// ❌ Lambda dài 15+ dòng — khó đọc, khó debug
std::sort(orders.begin(), orders.end(),
[&config, &db, &logger](const Order& a, const Order& b) {
auto priority_a = db.get_priority(a.customer);
auto priority_b = db.get_priority(b.customer);
if (priority_a != priority_b) {
logger.log("Sorting by priority");
return priority_a > priority_b;
}
// ... 10 dòng nữa ...
});
// ✅ Extract thành named function
bool compare_orders(const Order& a, const Order& b) {
// Logic rõ ràng, có thể unit test riêng
if (a.priority != b.priority) return a.priority > b.priority;
return a.amount > b.amount;
}
std::sort(orders.begin(), orders.end(), compare_orders);Anti-Pattern 3: Capture [&] mặc định
cpp
// ❌ Capture everything by reference — nguy hiểm và không rõ ràng
auto process = [&]() {
// Biến nào đang được dùng? Không ai biết!
// Nếu lambda sống lâu hơn scope → crash
};
// ✅ Capture rõ ràng từng biến
auto process = [&orders, threshold]() {
// Rõ ràng: dùng orders (by ref) và threshold (by value)
};🎭 13. Scenario: Refactor Legacy Code
🎭 Tình huống thực tế
Bạn nhận được đoạn code legacy sau từ đồng nghiệp. Hãy refactor bằng STL algorithms + lambdas.
Code legacy:
cpp
// Legacy code — 25 dòng, khó đọc, dễ bug
std::vector<Product> get_report(const std::vector<Product>& products,
double min_price) {
std::vector<Product> result;
for (int i = 0; i < products.size(); i++) {
if (products[i].price >= min_price && products[i].in_stock) {
result.push_back(products[i]);
}
}
// Bubble sort (!)
for (int i = 0; i < result.size(); i++) {
for (int j = i + 1; j < result.size(); j++) {
if (result[i].price > result[j].price) {
Product temp = result[i];
result[i] = result[j];
result[j] = temp;
}
}
}
double total = 0;
for (int i = 0; i < result.size(); i++) {
total += result[i].price;
}
std::cout << "Total: " << total << "\n";
return result;
}Refactored:
cpp
// Modern C++ — 12 dòng, rõ ý, nhanh hơn
std::vector<Product> get_report(const std::vector<Product>& products,
double min_price) {
std::vector<Product> result;
std::copy_if(products.begin(), products.end(),
std::back_inserter(result),
[min_price](const Product& p) {
return p.price >= min_price && p.in_stock;
});
std::sort(result.begin(), result.end(),
[](const Product& a, const Product& b) {
return a.price < b.price;
});
double total = std::accumulate(result.begin(), result.end(), 0.0,
[](double sum, const Product& p) { return sum + p.price; });
std::cout << "Total: " << total << "\n";
return result;
}Cải thiện:
- ✅
copy_ifthay vì loop + if → rõ ý định "lọc" - ✅
std::sort(introsort O(n log n)) thay vì bubble sort O(n²) - ✅
accumulatethay vì loop tính tổng → không thể sai index - ✅ Ít dòng hơn, ít chỗ để bug ẩn nấp
✅ 14. Module 3 Complete — Checkpoint
🎉 Module 3: STL — Standard Weapons hoàn tất!
Bạn đã nắm vững:
| Bài | Nội dung | Kỹ năng |
|---|---|---|
| 07 — STL Containers | vector, map, set, unordered_map | Chọn container đúng cho bài toán |
| 08 — Algorithms & Lambdas (bài này) | sort, find, transform, lambdas | Viết code ngắn gọn, biểu cảm |
Bạn đã có thể:
- ✅ Chọn container phù hợp (Module 3, Bài 7)
- ✅ Dùng STL algorithms thay raw loops
- ✅ Viết lambdas với capture semantics đúng
- ✅ Áp dụng Erase-Remove idiom
- ✅ Biết C++20 Ranges preview
Tiếp theo: Phụ lục — Memory Mistakes & STL vs Manual — tổng hợp những lỗi memory phổ biến và so sánh STL với cách làm thủ công.
🧠 Quiz
Câu 1: Lambda capture [&x] nghĩa là gì?
- [ ] A) Capture biến
xby value (tạo bản sao) - [x] B) Capture biến
xby reference (tham chiếu đến biến gốc) - [ ] C) Capture tất cả biến by reference
- [ ] D) Capture địa chỉ của
xdưới dạng pointer
💡 Giải thích:
[&x]capture biếnxbằng tham chiếu — lambda truy cập trực tiếp biến gốc. Thay đổixtrong lambda sẽ thay đổi biến bên ngoài. Cẩn thận: nếu lambda sống lâu hơnx, bạn sẽ có dangling reference!
Câu 2: std::remove(v.begin(), v.end(), 42) làm gì?
- [ ] A) Xóa tất cả phần tử có giá trị 42 và giảm
v.size() - [x] B) Dồn các phần tử khác 42 lên đầu, trả về iterator đến "vùng rác" —
v.size()KHÔNG đổi - [ ] C) Đánh dấu các phần tử 42 là deleted, cần gọi
v.compact()sau - [ ] D) Ném exception nếu không tìm thấy 42
💡 Giải thích:
std::removelà algorithm hoạt động trên range, không biết về container. Nó dồn phần tử "giữ lại" lên đầu và trả về iterator đến phần tử đầu tiên không cần giữ. Phải kết hợp vớiv.erase(it, v.end())để thực sự xóa — đây là Erase-Remove Idiom. C++20 cóstd::erase(v, 42)tiện hơn.
Câu 3: Đoạn code sau in ra giá trị gì?
cpp
std::vector<double> v = {1.7, 2.3, 3.9};
auto result = std::accumulate(v.begin(), v.end(), 0);
std::cout << result;- [ ] A) 7.9
- [x] B) 6
- [ ] C) 7
- [ ] D) Compilation error
💡 Giải thích: Init value
0có kiểuint, nênaccumulatetrả vềint. Mỗi lần cộng:0 + 1.7 → 1(truncate),1 + 2.3 → 3,3 + 3.9 → 6. Kết quả là6, không phải7.9! Fix: dùng0.0làm init value để giữ kiểudouble.