Vítejte zpátky v C++ – moderní jazyk C++

Od svého vytvoření se jazyk C++ stal jedním z nejpoužívanějších programovacích jazyků na světě. Dobře napsané programy C++ jsou rychlé a efektivní. Jazyk je flexibilnější než jiné jazyky: Může pracovat na nejvyšších úrovních abstrakce a dolů na úrovni silikonu. C++ poskytuje vysoce optimalizované standardní knihovny. Umožňuje přístup k hardwarovým funkcím nízké úrovně, aby maximalizoval rychlost a minimalizoval požadavky na paměť. C++ může vytvářet téměř jakýkoli druh programu: Hry, ovladače zařízení, HPC, cloud, desktop, vložené a mobilní aplikace a mnoho dalšího. I knihovny a kompilátory pro jiné programovací jazyky se píšou v jazyce C++.

Jedním z původních požadavků pro jazyk C++ byla zpětná kompatibilita s jazykem C. V důsledku toho jazyk C++ vždy povolil programování ve stylu jazyka C s nezpracovanými ukazateli, poli, řetězci znaků ukončenými hodnotou null a dalšími funkcemi. Můžou umožnit skvělý výkon, ale můžou také vytvořit chyby a složitost. Vývoj jazyka C++ zdůraznil funkce, které výrazně snižují potřebu používat idiomy ve stylu jazyka C. Staré programovací zařízení C jsou stále tam, když je potřebujete. V moderním kódu jazyka C++ byste je ale měli potřebovat méně a méně. Moderní kód jazyka C++ je jednodušší, bezpečnější, elegantnější a stále tak rychlý jako kdykoli předtím.

Následující části obsahují přehled hlavních funkcí moderního jazyka C++. Pokud není uvedeno jinak, jsou zde uvedené funkce k dispozici v jazyce C++11 a novějším. V kompilátoru jazyka Microsoft C++ můžete nastavit možnost kompilátoru /std , abyste určili, jakou verzi standardu se má pro váš projekt použít.

Prostředky a inteligentní ukazatele

Jednou z hlavních tříd chyb v programování ve stylu jazyka C je nevracení paměti. Nevracení je často způsobeno selháním volání delete paměti, která byla přidělena new. Moderní jazyk C++ zdůrazňuje princip získávání prostředků inicializací (RAII). Myšlenka je jednoduchá. Prostředky (paměť haldy, popisovače souborů, sokety atd.) by měly vlastnit objekt. Tento objekt vytvoří nebo přijme nově přidělený prostředek v jeho konstruktoru a odstraní ho v jeho destruktoru. Princip RAII zaručuje, že všechny prostředky se správně vrátí do operačního systému, když vlastní objekt zmizí z rozsahu.

Pro podporu snadného přijetí principů RAII poskytuje standardní knihovna C++ tři typy inteligentních ukazatelů: std::unique_ptr, std::shared_ptra std::weak_ptr. Inteligentní ukazatel zpracovává přidělení a odstranění paměti, která vlastní. Následující příklad ukazuje třídu s členem pole, který je přidělen na haldě ve volání make_unique(). Volání new a delete zapouzdření unique_ptr třídou. widget Když objekt přestane být oborem, vyvolá se unique_ptr destruktor a uvolní paměť přidělenou pro pole.

#include <memory>
class widget
{
private:
    std::unique_ptr<int[]> data;
public:
    widget(const int size) { data = std::make_unique<int[]>(size); }
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data gadget member
    // ...
    w.do_something();
    // ...
} // automatic destruction and deallocation for w and w.data

Kdykoli je to možné, použijte inteligentní ukazatel ke správě paměti haldy. Pokud je nutné explicitně používat operátory new a delete operátory, postupujte podle principu RAII. Další informace naleznete v tématu Životnost objektu a správa prostředků (RAII).

std::string a std::string_view

Řetězce ve stylu jazyka C jsou dalším hlavním zdrojem chyb. Pomocí std::string a std::wstringmůžete odstranit prakticky všechny chyby spojené s řetězci ve stylu jazyka C. Získáte také výhodu členských funkcí pro vyhledávání, připojování, předpending atd. Oba jsou vysoce optimalizované pro rychlost. Při předávání řetězce do funkce, která vyžaduje pouze přístup jen pro čtení, můžete v jazyce C++17 využít std::string_view ještě vyšší výhody výkonu.

std::vector a další kontejnery standardní knihovny

Všechny kontejnery standardní knihovny se řídí principem RAII. Poskytují iterátory pro bezpečné procházení prvků. A jsou vysoce optimalizované pro výkon a byly důkladně testovány na správnost. Pomocí těchto kontejnerů eliminujete potenciál chyb nebo neektivostí, které by mohly být zavedeny ve vlastních datových strukturách. Místo nezpracovaných polí použijte vector jako sekvenční kontejner v jazyce C++.

vector<string> apples;
apples.push_back("Granny Smith");

Jako výchozí asociativní kontejner použijte map (ne unordered_map). Použijte set, multimapa multiset pro degenerovat a více případů.

map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";

Pokud je potřeba optimalizace výkonu, zvažte použití:

  • Neseřazené asociativní kontejnery, například unordered_map. Mají nižší režii na jednotlivé prvky a vyhledávání v konstantním čase, ale jejich správné a efektivní použití může být obtížnější.
  • Seřazeno vector. Další informace naleznete v tématu Algoritmy.

Nepoužívejte pole ve stylu C. Pro starší rozhraní API, která potřebují přímý přístup k datům, použijte například metody f(vec.data(), vec.size()); přístupového objektu. Další informace o kontejnerech najdete v tématu Kontejnery standardní knihovny jazyka C++.

Algoritmy standardní knihovny

Než budete předpokládat, že potřebujete pro svůj program napsat vlastní algoritmus, nejprve si projděte algoritmy standardní knihovny C++. Standardní knihovna obsahuje stále rostoucí sortiment algoritmů pro mnoho běžných operací, jako je vyhledávání, řazení, filtrování a náhodná změna. Matematická knihovna je rozsáhlá. V jazyce C++17 a novějších jsou k dispozici paralelní verze mnoha algoritmů.

Tady je několik důležitých příkladů:

  • for_each, výchozí algoritmus procházení (společně se smyčkami založenými na for rozsahu).
  • transform, pro neumisťované úpravy prvků kontejneru
  • find_if, výchozí vyhledávací algoritmus.
  • sorta lower_bounddalší výchozí algoritmy řazení a vyhledávání.

Chcete-li napsat srovnávací program, použijte striktní < a pojmenované lambda, pokud je to možné.

auto comp = [](const widget& w1, const widget& w2)
     { return w1.weight() < w2.weight(); }

sort( v.begin(), v.end(), comp );

auto i = lower_bound( v.begin(), v.end(), widget{0}, comp );

auto místo explicitních názvů typů

Jazyk C++11 zavedl auto klíčové slovo pro použití v deklaracích proměnných, funkcích a šablonách. auto říká kompilátoru, aby odvodil typ objektu, abyste ho nemuseli explicitně zadávat. auto je zvlášť užitečné, pokud je vyvolaný typ vnořenou šablonou:

map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++

Smyčky založené na for rozsahu

Iterace ve stylu jazyka C u polí a kontejnerů je náchylná k chybám indexování a je také zdlouhavá pro typ. Chcete-li tyto chyby odstranit a usnadnit čtení kódu, použijte smyčky založené na for rozsahu s kontejnery standardní knihovny i nezpracovanými poli. Další informace naleznete v tématu Příkaz založený na for rozsahu.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v {1,2,3};

    // C-style
    for(int i = 0; i < v.size(); ++i)
    {
        std::cout << v[i];
    }

    // Modern C++:
    for(auto& num : v)
    {
        std::cout << num;
    }
}

constexpr výrazy místo maker

Makra v jazyce C a C++ jsou tokeny, které před kompilací zpracovává preprocesor. Každá instance tokenu makra se nahradí definovanou hodnotou nebo výrazem před kompilací souboru. Makra se běžně používají v programování ve stylu jazyka C k definování konstantních hodnot v čase kompilace. Makra jsou však náchylná k chybám a obtížně se ladí. V moderním jazyce C++ byste měli upřednostňovat constexpr proměnné pro konstanty kompilace:

#define SIZE 10 // C-style
constexpr int size = 10; // modern C++

Jednotná inicializace

V moderním jazyce C++ můžete použít inicializaci závorek pro libovolný typ. Tato forma inicializace je obzvláště vhodná při inicializaci polí, vektorů nebo jiných kontejnerů. V následujícím příkladu v2 je inicializován se třemi instancemi S. v3 inicializuje se třemi instancemi S , které jsou inicializovány pomocí složených závorek. Kompilátor odvodí typ každého prvku na základě deklarovaného typu v3.

#include <vector>

struct S
{
    std::string name;
    float num;
    S(std::string s, float f) : name(s), num(f) {}
};

int main()
{
    // C-style initialization
    std::vector<S> v;
    S s1("Norah", 2.7);
    S s2("Frank", 3.5);
    S s3("Jeri", 85.9);

    v.push_back(s1);
    v.push_back(s2);
    v.push_back(s3);

    // Modern C++:
    std::vector<S> v2 {s1, s2, s3};

    // or...
    std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };

}

Další informace naleznete v tématu Inicializace závorek.

Přesun sémantiky

Moderní jazyk C++ poskytuje sémantiku přesunutí, která umožňuje eliminovat nepotřebné kopie paměti. V dřívějších verzích jazyka nebyly kopie v určitých situacích nevyhnutelné. Operace přesunutí přenese vlastnictví prostředku z jednoho objektu na další, aniž by bylo nutné vytvořit kopii. Některé třídy vlastní prostředky, jako je paměť haldy, popisovače souborů atd. Při implementaci třídy vlastněné prostředkem můžete definovat konstruktor přesunutí a operátor přiřazení pro něj. Kompilátor zvolí tyto speciální členy během řešení přetížení v situacích, kdy není potřeba kopírovat. Typy kontejnerů standardní knihovny vyvolávají konstruktor přesunutí u objektů, pokud je definován. Další informace naleznete v tématu Přesunout konstruktory a operátory přiřazení přesunutí (C++).

Výrazy lambda

V programování ve stylu jazyka C lze funkci předat jiné funkci pomocí ukazatele funkce. Ukazatele funkcí jsou pro údržbu a pochopení nevhodné. Funkce, na kterou odkazují, může být definována jinde ve zdrojovém kódu, daleko od bodu, ve kterém je vyvolána. Také nejsou typově bezpečné. Moderní jazyk C++ poskytuje objekty funkcí, třídy, které přepíší operator() operátor, což jim umožňuje volat jako funkci. Nejpohodlnější způsob, jak vytvořit objekty funkcí, je s vloženými výrazy lambda. Následující příklad ukazuje, jak pomocí výrazu lambda předat objekt funkce, že find_if funkce vyvolá na každý prvek vektoru:

    std::vector<int> v {1,2,3,4,5};
    int x = 2;
    int y = 4;
    auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });

Výraz [=](int i) { return i > x && i < y; } lambda lze přečíst jako "funkce, která přebírá jeden argument typu int a vrací logickou hodnotu, která označuje, zda je argument větší než x a menší než y." Všimněte si, že proměnné x a y okolní kontext lze použít v lambda. Určuje [=] , že tyto proměnné jsou zachyceny hodnotou; jinými slovy výraz lambda má své vlastní kopie těchto hodnot.

Výjimky

Moderní C++ zdůrazňuje výjimky, nikoli kódy chyb, jako nejlepší způsob, jak hlásit a zpracovat chybové podmínky. Další informace najdete v tématu Moderní osvědčené postupy jazyka C++ pro výjimky a zpracování chyb.

std::atomic

Pro mechanismy komunikace mezi vlákny použijte strukturu standardní knihovny std::atomic C++ a související typy.

std::variant (C++17)

Sjednocení se běžně používají v programování ve stylu jazyka C, aby ušetřily paměť tím, že členům různých typů umožní zabírat stejné umístění paměti. Sjednocení ale nejsou typově bezpečné a jsou náchylné k programovacím chybám. C++17 zavádí std::variant třídu jako robustnější a bezpečnější alternativu ke sjednocením. Funkci std::visit lze použít pro přístup k členům variant typu bezpečným způsobem.

Viz také

Referenční dokumentace jazyka C++
Výrazy lambda
Standardní knihovna C++
Shoda jazyka Microsoft C/C++