Sari la conținut

C++17: Standardul practic

Explorați cele mai importante caracteristici ale C++17: legături structurate, std::optional/variant/any, std::string_view, std::filesystem, dacă constexpr și atribute noi.

De ce contează C++17

C++17 este adesea numit „standardul practic”, deoarece a introdus o mulțime de caracteristici care simplifică codarea de zi cu zi fără a necesita cunoștințe profunde de metaprogramare a șablonului. A eliminat bagajele vechi (cum ar fistd::auto_ptrși trigrafele), a adăugat tipuri de vocabular (std::optional,std::variant,std::any) și a introdus zahăr sintactic care face codul mai scurt și mai clar. Dacă scrieți astăzi C++ de producție, C++17 ar trebui să fie linia de bază minimă.

Legături structurate și declarații de inițializare

Legăturile structurate vă permit să despachetați agregate (structuri, matrice, perechi, tupluri) în variabile numite într-o singură declarație. Combinate cu inițializatoareleif/switch, acestea elimină multe variabile temporare:

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

Tipuri de vocabular: opțional, variantă, orice

std::optionalreprezintă o valoare care poate sau nu să fie prezentă (înlocuind valorile santinelă și parametrii de ieșire).std::varianteste o uniune sigură.std::anydeține orice tip copiabil cu verificarea tipului de rulare:

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 și sistem de fișiere

std::string_vieweste o referință ușoară, care nu deține un șir. Evită copiile atunci când trebuie să citiți doar un șir.std::filesystemoferă manipulare portabilă, multiplatformă a căilor și operații de fișiere:

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

dacă constexpr, CTAD și Mai mult

if constexprpermite ramificarea în timp de compilare în interiorul șabloanelor - ramurile care nu se potrivesc sunt eliminate în întregime. Class Template Argument Deduction (CTAD) vă permite să omiteți argumentele șablon atunci când compilatorul le poate deduce. Fold expresiile simplifică pachetele de parametri de șablon variadic:

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;
}
Bună practică

C++17 a standardizat trei atribute importante:

[[nodiscard]] — Avertizează dacă o valoare returnată este eliminată. Folosiți-l pe funcții a căror valoare de returnare ar trebui întotdeauna verificată (coduri de eroare, resurse alocate, funcții din fabrică).

[[maybe_unused]] — Suprimă avertismentele privind variabile/parametrii neutilizate. Util pentru variabilele utilizate numai în versiunile de depanare sau codul specific platformei.

[[fallthrough]] — Indică căderea intenționată într-un cazswitch, eliminând avertismentele compilatorului.

Adoptă[[nodiscard]]în mod agresiv pe API-urile tale. Un cod de eroare eliminat este o eroare care așteaptă să se întâmple.

Capcană

Never return astd::string_viewthat references a local string. Sincestring_viewdoes not own its data, the underlying string may be destroyed before the view is used, causing undefined behavior:

// 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 for return values (owning data).

Idei cheie
  • Structured bindings (auto [a, b] = ...) unpack tuples, pairs, structs, and arrays cleanly
  • std::optionalreplaces sentinel values;std::variantreplaces unsafe unions;std::anyfor truly dynamic types
  • std::string_viewevită copiile pentru accesul la șiruri de caractere doar pentru citire, dar ferește-te de referințele agățate
  • if constexprpermite ramificarea în timp de compilare în șabloane, eliminând boilerplate SFINAE
  • CTAD permite compilatorului să deducă argumente șablon:std::vector v{1,2,3}funcționează
  • Utilizați[[nodiscard]]pentru funcțiile ale căror valori returnate nu trebuie ignorate

Test — Verifică-ți cunoștințele

(15 XP)

1. Ce face`auto [x, y] = std::make_pair(1, 2.0);`în C++17?

2. Ce reprezintă`std::optional<int>`?

3. De ce este periculoasă întoarcerea unui`std::string_view`de la o funcție dacă face referire la un șir local?