Μετάβαση στο περιεχόμενο

C++17: Το Πρακτικό Πρότυπο

Εξερευνήστε τα πιο εντυπωσιακά χαρακτηριστικά του C++17: δομημένες συνδέσεις, std::optional/variant/any, std::string_view, std::filesystem, if constexpr και νέα χαρακτηριστικά.

Γιατί το C++17 έχει σημασία

Το C++17 αποκαλείται συχνά το "πρακτικό πρότυπο" επειδή εισήγαγε μια πληθώρα δυνατοτήτων που απλοποιούν την καθημερινή κωδικοποίηση χωρίς να απαιτούν βαθιά γνώση μεταπρογραμματισμού προτύπων. Αφαίρεσε τις παλαιού τύπου αποσκευές (όπωςstd::auto_ptrκαι τρίγραφα), πρόσθεσε τύπους λεξιλογίου (std::optional,std::variant,std::any) και εισήγαγε συντακτική ζάχαρη που κάνει τον κώδικα πιο σύντομο και σαφέστερο. Εάν γράφετε παραγωγή C++ σήμερα, το C++17 θα πρέπει να είναι η ελάχιστη γραμμή βάσης.

Δομημένες Δέσεις & Δηλώσεις Initializer

Οι δομημένες συνδέσεις σάς επιτρέπουν να αποσυσκευάζετε συγκεντρωτικά στοιχεία (δομές, πίνακες, ζεύγη, πλειάδες) σε ονομασμένες μεταβλητές σε μία μόνο δήλωση. Σε συνδυασμό με αρχικοποιητές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;
}

Τύποι λεξιλογίου: προαιρετικό, παραλλαγή, οποιοδήποτε

Τοstd::optionalαντιπροσωπεύει μια τιμή που μπορεί να υπάρχει ή να μην υπάρχει (αντικαθιστά τις τιμές φρουρού και τις παραμέτρους εξόδου). Τοstd::variantείναι μια ένωση με ασφάλεια τύπου. Το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 & σύστημα αρχείων

Το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;
}

εάν constexpr, CTAD και άλλα

Τοif constexprεπιτρέπει τη διακλάδωση στο χρόνο μεταγλώττισης εντός προτύπων — οι κλάδοι που δεν ταιριάζουν απορρίπτονται εντελώς. Το Class Template Argument Deduction (CTAD) σάς επιτρέπει να παραλείπετε ορίσματα προτύπου όταν ο μεταγλωττιστής μπορεί να τα συμπεράνει. Οι αναδιπλούμενες εκφράσεις απλοποιούν τα πακέτα παραμέτρων μεταβλητών προτύπων:

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, σίγαση των προειδοποιήσεων μεταγλωττιστή.

Υιοθετήστε το[[nodiscard]]επιθετικά στα API σας. Ένας κωδικός σφάλματος που απορρίφθηκε είναι ένα σφάλμα που περιμένει να συμβεί.

Παγίδα

Ποτέ μην επιστρέψετε ένα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επιτρέπει τη διακλάδωση χρόνου μεταγλώττισης σε πρότυπα, εξαλείφοντας το boilerplate SFINAE
  • Το CTAD επιτρέπει στον μεταγλωττιστή να συναγάγει ορίσματα προτύπου: Τοstd::vector v{1,2,3}απλώς λειτουργεί
  • Χρησιμοποιήστε το[[nodiscard]]σε συναρτήσεις των οποίων οι τιμές επιστροφής δεν πρέπει να αγνοηθούν

Κουίζ — Δοκιμάστε τις γνώσεις σας

(15 XP)

1. Τι κάνει το`auto [x, y] = std::make_pair(1, 2.0);`στο C++17;

2. What does`std::optional<int>`represent?

3. Why is returning a`std::string_view`from a function dangerous if it references a local string?