Vítá vás zpět jazyk C++ – moderní verze jazyka 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 jazyka C++ jsou rychlé a efektivní. Jazyk je flexibilnější než jiné jazyky: Může fungovat na nejvyšší úrovni abstrakce a na úrovni křemíku. Jazyk C++ poskytuje vysoce optimalizované standardní knihovny. Umožňuje přístup k hardwarovým funkcím nízké úrovně, aby se maximalizovala rychlost a minimalizovaly požadavky na paměť. Jazyk C++ může vytvořit téměř jakýkoli druh programu: hry, ovladače zařízení, prostředí HPC, cloud, desktop, vložené a mobilní aplikace a mnoho dalšího. V jazyce C++ se pište i knihovny a kompilátory pro jiné programovací jazyky.

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 povoluje programování ve stylu jazyka C s nezpracovaných ukazateli, poli, řetězci znaků zakončenými hodnotou null a dalšími funkcemi. Mohou umožnovat vysoký výkon, ale mohou také způsobit chyby a složitost. Vývoj jazyka C++ zdůrazňoval funkce, které výrazně snižují potřebu použití idiomů ve stylu jazyka C. Stará programovací zařízení jazyka C jsou stále k dispozici, 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 rychlejší.

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

Prostředky a inteligentní ukazatele

Jednou z hlavních tříd chyb v programování ve stylu jazyka C je nevrácená paměť. Nevrácené informace jsou často způsobeny selháním volání delete paměti, která byla přidělena pomocí new . Moderní jazyk C++ zdůrazňuje princip získávání prostředků je inicializace (RAII). Myšlenka je jednoduchá. Prostředky (paměť haldy, popisovače souborů, sokety atd.) by měly být vlastníkem objektu. Tento objekt vytvoří (nebo přijme) nově přidělený prostředek ve svém konstruktoru a odstraní ho ve svém destruktoru. Princip RAII zaručuje, že se všechny prostředky správně vrátí do operačního systému, když vlastnící objekt přejde mimo rozsah.

Pro podporu snadného přijetí principů RAII poskytuje standardní knihovna C++ tři typy inteligentních ukazatelů: std::unique_ptrstd::shared_ptr , a 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í a newdelete jsou zapouzdřena unique_ptr třídou . Když widget se objekt dostane mimo rozsah, unique_ptr destruktor třídy 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 musíte operátory a newdelete používat explicitně, postupujte podle principu RAII. Další informace najdete v tématu Životnost objektů 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í a std::wstringmůžete eliminovat prakticky všechny chyby spojené s řetězci ve stylu jazyka C. Získáte také výhody členských funkcí pro vyhledávání, připojování, předpřipravení atd. Obě jsou vysoce optimalizované pro rychlost. Při předávání řetězce funkci, která vyžaduje přístup jen pro čtení, můžete v jazyce C++17 použít ještě std::string_view větší výkon.

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

Všechny standardní kontejnery knihoven dodržují princip RAII. Poskytují iterátory pro bezpečné procházení prvků. A jsou vysoce optimalizované pro výkon a důkladně otestované na správnost. Použitím těchto kontejnerů eliminujete riziko chyb nebo nedostatků, které by mohly být zavedeny ve vlastních datových strukturách. Místo nezpracovaných polí použijte v vector jazyce C++ jako sekvenční kontejner.

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

Jako map výchozí unordered_map asociativní kontejner použijte (ne). Pro setmultimap degenerované a více případů použijte , a multiset .

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

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

  • Typ array při vkládání je důležitý, například jako člen třídy.

  • Neuspořádané asociativní kontejnery, například unordered_map . Ty mají nižší režijní náklady na prvek a vyhledávání v konstantním čase, ale může být obtížnější je správně a efektivně používat.

  • Seřazeno vector . Další informace najdete v tématu Algoritmy.

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

Algoritmy standardní knihovny

Než budete předpokládat, že potřebujete napsat vlastní algoritmus pro váš program, nejprve si prohlédněte algoritmy standardní knihovny C++. Standardní knihovna obsahuje neustále rostoucí řadu algoritmů pro mnoho běžných operací, jako je vyhledávání, řazení, filtrování a randomizace. Matematická knihovna je rozsáhlá. Od verze C++17 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 neupravování prvků kontejneru, které nejsou na místě

  • find_if– výchozí vyhledávací algoritmus.

  • sort, lower_bound a další výchozí algoritmy řazení a vyhledávání.

Pokud chcete napsat komparátor, použijte striktní výrazy a pojmenované << 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(), comp );

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

Jazyk C++11 zavedl klíčové slovo pro použití auto v deklaracích proměnných, funkcí a šablon. auto říká kompilátoru, aby odvodit typ objektu, takže ho nemusíte zazadat explicitně. auto je zvlášť užitečný, pokud je dedukovaný 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 pro pole a kontejnery je náchylná k chybám indexování a je také zdlouhavá k psaní. Pokud chcete tyto chyby eliminovat a zajistit čitelnost kódu, použijte smyčky založené na rozsahu s kontejnery standardní knihovny i for nezpracovaných polí. Další informace najdete v tématu Příkaz založený na 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é preprocesor zpracovává před kompilací. Každá instance tokenu makra je před kompilován souborem nahrazena definovanou hodnotou nebo výrazem. Makra se běžně používají při programování ve stylu jazyka C k definování konstantních hodnot v době kompilace. Makra jsou však náchylná k chybám a obtížně se ladí. V moderním jazyce C++ byste měli constexpr upřednostňovat proměnné pro konstanty v době kompilace:

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

Jednotná inicializace

V moderním jazyce C++ můžete pro libovolný typ použít inicializaci složených závorek. Tato forma inicializace je obzvláště praktická 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 se inicializuje se třemi instancemi S , které jsou samy 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 najdete v tématu Inicializace složených závorek.

Sémantika přesunu

Moderní jazyk C++ poskytuje sémantiku přesunu, která umožňuje eliminovat nepotřebné kopie paměti. V dřívějších verzích jazyka byly kopie v určitých situacích nevyhnutelné. Operace přesunutí přenese vlastnictví prostředku z jednoho objektu na další bez vytvoření kopie. Některé třídy vlastní prostředky, například paměť haldy, popisovače souborů atd. Když implementujete třídu vlastnící prostředek, můžete pro něj definovat konstruktor přesunutí a operátor přiřazení přesunutí. Kompilátor zvolí tyto speciální členy během řešení přetížení v situacích, kdy není kopírování potřeba. Typy kontejnerů standardní knihovny vyvolaly konstruktor přesunutí objektů, pokud je definovaný. Další informace naleznete v tématu Move Constructors and Move Assignment Operators (C++).

Výrazy lambda

V programování ve stylu jazyka C může být funkce předána do jiné funkce pomocí ukazatele na funkci. Ukazatele na funkce jsou nepohodlné, aby je bylo možné udržovat a pochopit. Funkce, na kterou se odkazuje, mohou být definovány jinde ve zdrojovém kódu, od okamžiku, kdy se vyvolala. Nejedná se také o typově bezpečný. Moderní jazyk C++ poskytuje objekty funkce, třídy, které přepisují operátor, což umožňuje jejich volání jako funkce. Nejpohodlnější způsob, jak vytvořit objekty funkce, je pomocí vložených výrazů lambda. Následující příklad ukazuje, jak použít výraz lambda k předání objektu funkce, který for_each funkce vyvolá u každého prvku ve 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 lambda [=](int i) { return i > x && i < y; } lze číst jako funkci, která přijímá 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 z okolního kontextu lze použít ve výrazu lambda. [=]Určuje, že tyto proměnné jsou [=] hodnotou; jinými slovy, výraz lambda má své vlastní kopie těchto hodnot.

Výjimky

Moderní C++ zvýrazňuje výjimky, nikoli kódy chyb, jako nejlepší způsob, jak vykázat a zpracovávat chybové stavy. Další informace najdete v tématu moderní osvědčené postupy jazyka C++ pro výjimky a zpracování chyb.

std::atomic

Použijte strukturu standardní knihovny jazyka C++ std::atomic a související typy pro komunikační mechanismy mezi vlákny.

std::variant (C++ 17)

Sjednocení jsou běžně používána v programování ve stylu jazyka C k úspoře paměti tím, že umožňují členům různých typů zabírat stejné umístění v paměti. Sjednocení však nejsou typově bezpečná a jsou náchylná k chybám programování. C++ 17 zavádí std::variant třídu jako robustnější a bezpečnou alternativu pro sjednocení. std::visitFunkci lze použít pro přístup k členům variant typu typově bezpečným způsobem.

Viz také

Reference jazyka C++
Výrazy lambda
Standardní knihovna C++
Shoda v jazyce Microsoft C/C++