Container Adaptors و کانتینرهای ویژه
درباره stack، queue، priority_queue و نوعهای واژگانی مدرن مانند span، string_view و optional بیاموزید. بفهمید چه زمانی آداپتورها کد شما را سادهتر میکنند.
آداپتورهای کانتینر چیستند؟
آداپتورهای کانتینر (Container Adaptors) پوششهایی روی کانتینرهای موجود هستند که یک رابط محدود ارائه میدهند. آنها یک الگوی دسترسی خاص را با پنهان کردن API کامل کانتینر اعمال میکنند:
- std::stack — LIFO (آخرین ورودی، اولین خروجی)، به طور پیشفرض با deque پشتیبانی میشود
- std::queue — FIFO (اولین ورودی، اولین خروجی)، به طور پیشفرض با deque پشتیبانی میشود
- std::priority_queue — هرم بیشینه (max-heap)، به طور پیشفرض با vector پشتیبانی میشود
آداپتورها تکرارگر ارائه نمیدهند — فقط میتوانید به عنصر بالا/جلو دسترسی داشته باشید. این محدودیت عمدی است: هدف کد را واضحتر میکند و از سوءاستفاده تصادفی جلوگیری میکند.
Stack و Queue در عمل
پشتهها و صفها ساختارهای داده بنیادی هستند که در تجزیه (parsing)، BFS/DFS، سیستمهای بازگشت (undo) و زمانبندی وظایف استفاده میشوند.
#include <stack>
#include <queue>
#include <string>
#include <iostream>
// Check if parentheses are balanced using a stack
bool is_balanced(const std::string& expr) {
std::stack<char> stk;
for (char c : expr) {
if (c == '(' || c == '[' || c == '{') {
stk.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (stk.empty()) return false;
char top = stk.top();
stk.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stk.empty();
}
int main() {
std::cout << std::boolalpha;
std::cout << is_balanced("({[]})") << '\n'; // true
std::cout << is_balanced("({[}])") << '\n'; // false
// Queue — BFS-style task processing
std::queue<std::string> tasks;
tasks.push("compile");
tasks.push("link");
tasks.push("run tests");
while (!tasks.empty()) {
std::cout << "Processing: " << tasks.front() << '\n';
tasks.pop(); // removes from front
}
}صف اولویتدار (هرم)
std::priority_queue به طور پیشفرض یک هرم بیشینه (max-heap) است — top() بزرگترین عنصر را برمیگرداند. برای هرم کمینه (min-heap)، از std::greater به عنوان مقایسهکننده استفاده کنید.
#include <queue>
#include <vector>
#include <functional>
#include <iostream>
#include <string>
struct Task {
int priority;
std::string name;
};
int main() {
// Max-heap (default) — largest first
std::priority_queue<int> max_heap;
max_heap.push(30);
max_heap.push(10);
max_heap.push(50);
max_heap.push(20);
while (!max_heap.empty()) {
std::cout << max_heap.top() << ' '; // 50 30 20 10
max_heap.pop();
}
std::cout << '\n';
// Min-heap — smallest first
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
min_heap.push(30);
min_heap.push(10);
min_heap.push(50);
std::cout << "Min: " << min_heap.top() << '\n'; // 10
// Custom comparator with lambda
auto cmp = [](const Task& a, const Task& b) {
return a.priority < b.priority; // higher priority first
};
std::priority_queue<Task, std::vector<Task>, decltype(cmp)> task_queue(cmp);
task_queue.push({3, "low priority"});
task_queue.push({10, "critical"});
task_queue.push({7, "medium"});
while (!task_queue.empty()) {
auto& t = task_queue.top();
std::cout << "[" << t.priority << "] " << t.name << '\n';
task_queue.pop();
}
}std::span — نمای غیرمالک داده پیوسته (C++20)
std::span یک نمای سبک و غیرمالک روی دنبالهای پیوسته از عناصر است. الگوی مستعد خطای ارسال جفتهای (اشارهگر، اندازه) را جایگزین میکند.
#include <span>
#include <vector>
#include <array>
#include <iostream>
#include <numeric>
// span accepts vector, array, C-array — anything contiguous
void print_stats(std::span<const int> data) {
if (data.empty()) return;
std::cout << "Size: " << data.size() << '\n';
std::cout << "First: " << data.front() << '\n';
std::cout << "Last: " << data.back() << '\n';
int sum = std::accumulate(data.begin(), data.end(), 0);
std::cout << "Sum: " << sum << '\n';
// Subviews
auto first3 = data.first(3); // first 3 elements
auto last2 = data.last(2); // last 2 elements
auto mid = data.subspan(1, 3); // 3 elements starting at index 1
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::array<int, 5> arr = {10, 20, 30, 40, 50};
int c_arr[] = {100, 200, 300, 400, 500};
print_stats(vec); // works with vector
print_stats(arr); // works with array
print_stats(c_arr); // works with C-array
}string_view و optional
std::string_view (C++17) یک ارجاع غیرمالک و فقطخواندنی به یک رشته است. هزینه کپی یا تخصیص std::string را برای توابعی که فقط نیاز به خواندن دارند حذف میکند:
void greet(std::string_view name) { // no copy, no allocation
std::cout << "Hello, " << name << '\n';
}
greet("world"); // works with string literals
greet(std::string{"world"}); // works with std::stringstd::optional (C++17) نشاندهنده مقداری است که ممکن است وجود داشته باشد یا نداشته باشد — جایگزین ایمن از نظر نوع برای «مقادیر جادویی» یا اشارهگرهای خام برای نشان دادن عدم وجود:
std::optional<int> find_index(std::span<const int> data, int target) {
for (int i = 0; i < data.size(); ++i)
if (data[i] == target) return i;
return std::nullopt; // not found
}
auto idx = find_index(vec, 42);
if (idx) std::cout << "Found at " << *idx << '\n';
std::cout << idx.value_or(-1) << '\n'; // -1 if emptyاز آنجایی که std::string_view و std::span غیرمالک هستند، اگر داده زیربنایی نابود شود به راحتی آویزان میشوند:
std::string_view bad() {
std::string temp = "hello";
return temp; // DANGLING — temp destroyed at end of scope
}هرگز string_view یا span به یک متغیر محلی را برنگردانید. آنها برای پارامترهای تابع امن هستند (داده فراخواننده بیشتر از فراخوانی عمر دارد)، اما به عنوان مقادیر بازگشتی خطرناک هستند مگر اینکه بتوانید تضمین کنید منبع بیشتر از نما عمر دارد.
همین مسئله برای std::span نیز صدق میکند — در داخل فقط یک اشارهگر و یک اندازه است، بدون معنای مالکیت.
از آداپتورهای کانتینر استفاده کنید وقتی میخواهید نظمی را اعمال کنید بر نحوه دسترسی به عناصر:
- به جای deque یا vector خام، وقتی فقط دسترسی LIFO معنادار است از std::stack استفاده کنید. این هدف را واضح میکند و از دسترسی تصادفی اتفاقی جلوگیری میکند.
- برای پردازش FIFO (صف وظایف، BFS) از std::queue استفاده کنید.
- برای زمانبندی، الگوریتم دایکسترا یا هر سناریویی که همیشه عنصر حدی را میخواهید از std::priority_queue استفاده کنید.
اگر به پیمایش، پرسوجوی اندازه در نقاط دلخواه یا دسترسی میانی نیاز دارید، از کانتینر زیربنایی مستقیماً استفاده کنید.
- آداپتورهای کانتینر (
stack،queue،priority_queue) رابط را محدود میکنند تا نظم استفاده را اعمال کنند std::priority_queueبه طور پیشفرض یک هرم بیشینه است — برای هرم کمینه ازstd::greaterاستفاده کنیدstd::span(C++20) یک نمای غیرمالک روی داده پیوسته فراهم میکند و جفتهای(اشارهگر، اندازه)را جایگزین میکندstd::string_view(C++17) از کپی رشتهها برای استفاده فقطخواندنی جلوگیری میکند — اما مراقب ارجاعات آویزان باشیدstd::optional(C++17) روش ایمن از نظر نوع برای نمایش «شاید بدون مقدار» بدون مقادیر نگهبان جادویی است- نماهای غیرمالک (
span،string_view) هرگز نباید بیشتر از دادهای که به آن ارجاع میدهند عمر کنند
آزمون — دانش خود را بسنجید
(15 امتیاز)1. کدام آداپتور کانتینر رفتار FIFO (اولین ورودی، اولین خروجی) ارائه میدهد؟
2. `std::priority_queue<int>` به طور پیشفرض از `top()` چه نوعی برمیگرداند؟
3. چرا بازگرداندن `std::string_view` به یک `std::string` محلی خطرناک است؟