رفتن به محتوا

‎C++‎17: استاندارد کاربردی

ویژگی‌های تأثیرگذار ‎C++‎17 را کاوش کنید: اتصال‌های ساختاریافته (Structured Bindings)، std::optional/variant/any، std::string_view، std::filesystem، if constexpr، و ویژگی‌های جدید.

چرا ‎C++‎17 اهمیت دارد

‎C++‎17 اغلب «استاندارد کاربردی» نامیده می‌شود زیرا مجموعه‌ای غنی از ویژگی‌ها را معرفی کرد که کدنویسی روزمره را بدون نیاز به دانش عمیق متاپروگرمینگ قالب‌ها (Template Metaprogramming) ساده‌تر می‌کند. میراث‌های قدیمی (مانند std::auto_ptr و trigraphs) را حذف کرد، انواع واژگانی (std::optional، std::variant، std::any) را اضافه کرد، و قند نحوی ارائه داد که کد را هم کوتاه‌تر و هم واضح‌تر می‌سازد. اگر امروز کد ‎C++‎ تولیدی می‌نویسید، ‎C++‎17 باید حداقل خط مبنای شما باشد.

اتصال‌های ساختاریافته و دستورات مقداردهی اولیه

اتصال‌های ساختاریافته (Structured Bindings) به شما امکان می‌دهد مجموعه‌ها (ساختارها، آرایه‌ها، جفت‌ها، تاپل‌ها) را در یک اعلان واحد به متغیرهای نام‌گذاری شده باز کنید. در ترکیب با مقداردهنده‌های if/switch، بسیاری از متغیرهای موقتی حذف می‌شوند:

structured_bindings.cpp
#include <iostream>
#include <map>
#include <string>
#include <tuple>

std::tuple<std::string, int, double> get_employee() {
    return {"Alice", 42, 95000.0};
}

int main() {
    // Structured binding with tuple
    auto [name, age, salary] = get_employee();
    std::cout << name << " is " << age << " years old\n";

    // Structured binding with map iteration
    std::map<std::string, int> scores{{"Alice", 95}, {"Bob", 87}};
    for (const auto& [student, score] : scores) {
        std::cout << student << ": " << score << "\n";
    }

    // if with initializer — the variable is scoped to the if/else
    if (auto it = scores.find("Alice"); it != scores.end()) {
        std::cout << "Found: " << it->second << "\n";
    } else {
        std::cout << "Not found\n";
    }
    // 'it' no longer exists here — no scope leakage

    // Structured binding with array
    int arr[3] = {10, 20, 30};
    auto [x, y, z] = arr;
    std::cout << x << ", " << y << ", " << z << "\n";

    return 0;
}

انواع واژگانی: optional، variant، any

std::optional مقداری را نمایش می‌دهد که ممکن است وجود داشته باشد یا نداشته باشد (جایگزین مقادیر نگهبان و پارامترهای خروجی). std::variant یک اتحاد (union) ایمن از نظر نوع است. std::any هر نوع قابل کپی را با بررسی نوع در زمان اجرا نگه می‌دارد:

vocabulary_types.cpp
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string>

// optional: replaces "return -1 on failure" patterns
std::optional<int> find_index(const std::string& haystack, char needle) {
    for (size_t i = 0; i < haystack.size(); ++i) {
        if (haystack[i] == needle) return static_cast<int>(i);
    }
    return std::nullopt;  // no value
}

int main() {
    // std::optional
    auto idx = find_index("hello", 'l');
    if (idx.has_value()) {
        std::cout << "Found at index " << *idx << "\n";       // 2
    }
    std::cout << idx.value_or(-1) << "\n";  // safe default

    // std::variant — type-safe union
    std::variant<int, double, std::string> value = "hello";
    std::cout << std::get<std::string>(value) << "\n";

    // Visit pattern — exhaustive handling of all types
    std::visit([](const auto& v) {
        std::cout << "Value: " << v << "\n";
    }, value);

    value = 3.14;  // now holds a double
    std::cout << std::get<double>(value) << "\n";

    // std::any — truly any type, with runtime checking
    std::any data = 42;
    std::cout << std::any_cast<int>(data) << "\n";
    data = std::string("world");
    std::cout << std::any_cast<std::string>(data) << "\n";

    return 0;
}

string_view و filesystem

std::string_view یک ارجاع سبک و غیرمالک به یک رشته است. از کپی‌های غیرضروری هنگامی که فقط نیاز به خواندن رشته دارید جلوگیری می‌کند. std::filesystem عملیات مسیر و فایل قابل حمل و چندسکویی ارائه می‌دهد:

string_view_fs.cpp
#include <iostream>
#include <string>
#include <string_view>
#include <filesystem>

namespace fs = std::filesystem;

// string_view avoids copying — just a pointer + length
void print_trimmed(std::string_view sv) {
    auto start = sv.find_first_not_of(' ');
    auto end = sv.find_last_not_of(' ');
    if (start != std::string_view::npos) {
        std::cout << sv.substr(start, end - start + 1) << "\n";
    }
}

int main() {
    std::string name = "  Hello, C++17!  ";
    print_trimmed(name);       // no copy — string_view binds to string
    print_trimmed("  world  "); // no copy — binds to string literal

    // std::filesystem — portable path operations
    fs::path source_dir = "/home/user/project/src";
    std::cout << "Filename: " << source_dir.filename() << "\n";
    std::cout << "Parent:   " << source_dir.parent_path() << "\n";
    std::cout << "Exists:   " << fs::exists(source_dir) << "\n";

    // Iterate directory entries
    for (const auto& entry : fs::directory_iterator(".")) {
        if (entry.is_regular_file()) {
            std::cout << entry.path().filename()
                      << " (" << entry.file_size() << " bytes)\n";
        }
    }

    // Create directories recursively
    fs::create_directories("output/logs/2024");

    return 0;
}

if constexpr، CTAD، و موارد بیشتر

if constexpr انشعاب زمان کامپایل را در داخل قالب‌ها (Templates) فعال می‌کند — شاخه‌هایی که مطابقت ندارند کاملاً حذف می‌شوند. استنتاج آرگومان قالب کلاس (CTAD) به شما امکان می‌دهد آرگومان‌های قالب را هنگامی که کامپایلر قادر به استنتاج آن‌هاست حذف کنید. عبارات تاشو (Fold Expressions) بسته‌های پارامتر قالب متغیر را ساده‌تر می‌کنند:

if_constexpr.cpp
#include <iostream>
#include <string>
#include <vector>
#include <type_traits>

// if constexpr — branches resolved at compile time
template<typename T>
std::string to_string_safe(T value) {
    if constexpr (std::is_arithmetic_v<T>) {
        return std::to_string(value);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return value;
    } else {
        return "[unsupported type]";
    }
}

// Fold expressions — collapse parameter packs
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // unary right fold
}

template<typename... Args>
void print_all(Args&&... args) {
    ((std::cout << args << " "), ...);  // fold over comma
    std::cout << "\n";
}

int main() {
    std::cout << to_string_safe(42) << "\n";
    std::cout << to_string_safe(std::string("hi")) << "\n";

    std::cout << sum(1, 2, 3, 4, 5) << "\n";  // 15
    print_all(1, "hello", 3.14);               // 1 hello 3.14

    // CTAD — no need for std::vector<int>
    std::vector v{1, 2, 3, 4, 5};   // deduced as vector<int>
    std::pair p{"hello", 42};        // deduced as pair<const char*, int>

    // Nested namespaces
    // namespace A::B::C { } instead of namespace A { namespace B { namespace C { } } }

    // Inline variables — can be defined in headers without ODR violations
    // inline constexpr int version = 17;

    return 0;
}
بهترین شیوه

‎C++‎17 سه ویژگی مهم را استانداردسازی کرد:

[[nodiscard]] — هشدار می‌دهد اگر مقدار بازگشتی نادیده گرفته شود. آن را روی توابعی استفاده کنید که مقدار بازگشتی‌شان باید همیشه بررسی شود (کدهای خطا، منابع تخصیص‌یافته، توابع کارخانه‌ای).

[[maybe_unused]] — هشدارهای متغیر/پارامتر استفاده‌نشده را سرکوب می‌کند. برای متغیرهایی که فقط در بیلدهای اشکال‌زدایی یا کدهای خاص پلتفرم استفاده می‌شوند مفید است.

[[fallthrough]] — نشان‌دهنده عبور عمدی در یک switch case است و هشدارهای کامپایلر را خاموش می‌کند.

[[nodiscard]] را به‌شدت در APIهای خود به‌کار ببرید. یک کد خطای نادیده‌گرفته‌شده، باگی است که منتظر وقوع است.

دام

هرگز یک std::string_view را که به یک رشته محلی ارجاع می‌دهد برنگردانید. از آنجا که string_view مالک داده‌هایش نیست، رشته زیربنایی ممکن است قبل از استفاده از نما (view) تخریب شود و باعث رفتار تعریف‌نشده شود:

// DANGEROUS — returns a view to a destroyed temporary!
std::string_view bad() {
    std::string s = "hello";
    return s;  // s is destroyed at end of function!
}

قاعده کلی: از string_view برای پارامترها (خواندن داده) و از std::string برای مقادیر بازگشتی (مالکیت داده) استفاده کنید.

نکات کلیدی
  • اتصال‌های ساختاریافته (auto [a, b] = ...) تاپل‌ها، جفت‌ها، ساختارها و آرایه‌ها را به‌صورت تمیز باز می‌کنند
  • std::optional جایگزین مقادیر نگهبان؛ std::variant جایگزین اتحادهای ناامن؛ std::any برای انواع کاملاً پویا
  • std::string_view از کپی برای دسترسی فقط‌خواندنی به رشته جلوگیری می‌کند — اما مراقب ارجاعات آویزان باشید
  • if constexpr انشعاب زمان کامپایل را در قالب‌ها فعال می‌کند و کدهای اضافی SFINAE را حذف می‌کند
  • CTAD به کامپایلر اجازه می‌دهد آرگومان‌های قالب را استنتاج کند: std::vector v{1,2,3} به‌سادگی کار می‌کند
  • از [[nodiscard]] روی توابعی استفاده کنید که مقادیر بازگشتی‌شان نباید نادیده گرفته شوند

آزمون — دانش خود را بسنجید

(15 امتیاز)

1. عبارت `auto [x, y] = std::make_pair(1, 2.0);` در ‎C++‎17 چه کاری انجام می‌دهد؟

2. `std::optional<int>` چه چیزی را نمایش می‌دهد؟

3. چرا برگرداندن `std::string_view` از یک تابع در صورت ارجاع به رشته محلی خطرناک است؟