C++17: المعيار العملي
اكتشف ميزات C++17 الأكثر تأثيرًا: الارتباطات المنظمة، std::اختياري/variant/any، std::string_view، std::filesystem، if constexpr، والسمات الجديدة.
لماذا يهم C++17
غالبًا ما يُطلق على C++17 اسم "المعيار العملي" لأنه قدم مجموعة كبيرة من الميزات التي تعمل على تبسيط عملية الترميز اليومية دون الحاجة إلى معرفة عميقة ببرمجة القوالب الوصفية. لقد أزالت الأمتعة القديمة (مثلstd::auto_ptrوtrigraphs)، وأضافت أنواع المفردات (std::optional،std::variant،std::any)، وقدمت السكر النحوي الذي يجعل التعليمات البرمجية أقصر وأكثر وضوحًا. إذا كنت تكتب إنتاج C++ اليوم، فيجب أن يكون C++17 الحد الأدنى لخط الأساس.
الارتباطات المنظمة وبيانات التهيئة
تتيح لك الارتباطات المنظمة فك التجميعات (البنيات، والمصفوفات، والأزواج، والصفوف) في متغيرات مسماة في إعلان واحد. عند دمجها مع مُهيئاتif/switch، فإنها تقضي على العديد من المتغيرات المؤقتة:
#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;
}أنواع المفردات: اختياري، متغير، أي
يمثلstd::optionalقيمة قد تكون موجودة أو لا تكون موجودة (تحل محل القيم الحارسة ومعلمات الإخراج).std::variantهو اتحاد آمن من النوع. يحملstd::anyأي نوع قابل للنسخ مع التحقق من نوع وقت التشغيل:
#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 ونظام الملفات
std::string_viewهو مرجع خفيف الوزن وغير مالك لسلسلة. إنه يتجنب النسخ عندما تحتاج فقط إلى قراءة سلسلة. يوفرstd::filesystemإمكانية معالجة المسار وعمليات الملفات المحمولة عبر الأنظمة الأساسية:
#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;
}إذا constexpr، CTAD، وأكثر من ذلك
يتيحif constexprتفرع وقت الترجمة داخل القوالب - يتم تجاهل الفروع غير المتطابقة تمامًا. يتيح لك خصم وسيطة قالب الفئة (CTAD) حذف وسيطات القالب عندما يتمكن المترجم من استنتاجها. تعمل تعبيرات الطية على تبسيط حزم معلمات القالب المتغير:
#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، مما يؤدي إلى إسكات تحذيرات المترجم.
اعتمد[[nodiscard]]بقوة على واجهات برمجة التطبيقات الخاصة بك. رمز الخطأ المهمل هو خطأ ينتظر حدوثه.
أبدًا قم بإرجاعstd::string_viewالذي يشير إلى سلسلة محلية. نظرًا لأنstring_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!
}Rule of thumb: use string_view for parameters (reading data), return 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`من دالة أمرًا خطيرًا إذا كان يشير إلى سلسلة محلية؟