Warning
This page is located in archive.

Cvičení 12: Globální proměnné

Globální proměnné vyžadují podobné zacházení jako funkce – existují jejich definice a deklarace, a definice smí být jenom jedna. Platí, že v celém programu může být jenom jedna globální proměnná s daným názvem a typem.

  • Stáhněte si výchozí kód.
  • Udělejte z proměnné jidlo globální proměnnou.
  • Přidejte do hlavičky deklaraci proměnné jidlo.
  • Přidejte ve funkcích celer() a lilek() nové položky do vektoru jidlo.

Abychom napsali deklaraci globální proměnné, museli jsme na začátek přidat klíčové slovo extern. Také je žádoucí si globální proměnnou nějak označit, třeba ji přejmenovat na g_jidlo.

Řešení

Globální proměnné ve funkcích

  • Vytvořte funkci pocitadlo(), která vypíše, pokolikáté jsme ji zavolali. Udržuje si počet volání v globální proměnné.
  • Přesuňte globální proměnnou dovnitř funkce. Použijte klíčové slovo static.

Globální proměnné ve třídách

  • Vytvořte třídu (nebo strukturu) Zelenina.
  • Přesuňte funkce lilek a celer do třídy Zelenina a označte tyto funkce jako statické. Statické funkce nevyžadují přítomnost instance třídy a lze je zavolat přímo pomocí syntaxe Zelenina::lilek().
  • Přesuňte globální proměnnou jidlo do třídy Zelenina, použijte klíčové slovo static. Statická data také nevyžadují přítomnost instance třídy, jsou to vlastně globální proměnné. Odkážeme se na ně pomocí syntaxe Zelenina::jidlo.

Stejně jako předtím, i teď musíme proměnnou jídlo definovat, a to v právě jedné kompilační jednotce. Všimněte si, že to je stejné, jako když jidlo byla obyčejná globální proměnná.

  • Vytvořte třídu LisNaJesterkyAHady, která počítá, kolikrát byla zkonstruována a kolikrát byla zdestruována. Počet konstrukcí a destrukcí uchovávejte ve statických proměnných třídy. Statické proměnné zapouzdřete a přistupujte k nim pomocí statických metod LisNaJesterkyAHady::pocetKonstrukci() a LisNaJesterkyAHady::pocetDestrukci().

Řešení

Konstanty jednoduchých typů

Pro celočíselné konstanty (const int, const long, všechny konstantní enumy, apod.) platí výjimka z pravidla jedné definice – jako globální proměnné mohou být definovány opakovaně. Díky tomu můžeme do hlavičky umístit tento kód:

const int pocetDruhuOvoce = 130;
 
struct Zelenina {
    static const int pocetDruhuZeleniny = 131;
    ...
};

Některé další jednoduché typy lze také použít jako konstanty, pokud máme C++11. Někdy je ale nutné použít constexpr místo const (pokud nad tím nechcete přemýšlet, dá se psát všude constexpr).

const float pi = 3.14;
const int pocetDruhuOvoce = 130;
 
struct Zelenina {
    static constexpr float pi = 3.14f; // tady musí být constexpr
    static const int pocetDruhuZeleniny = 131;
    ...
};

Klíčové slovo constexpr obecně znamená něco jako “je to konstanta známá během kompilace”, zatímco const se blíží více významu “dá se nastavit jenom jednou a pak se nesmí měnit”.

Problém s pořadím inicializace

Špatná situace nastává ve chvíli, když potřebujete jednu globální proměnnou inicializovat na základě hodnoty v jiné globální proměnné. V rámci jedné kompilační jednotky se ještě všechno chová rozumně, globální proměnné jsou inicializovány v tom pořadí, v jakém jsou inicializace napsány. Problém nastane ve chvíli, když máme inicializace v různých kompilačních jednotkách:

// header.hpp
struct Zelenina {
    static std::vector<std::string> jidlo;
    static const int pocetDruhuNaZacatku;
}
 
// lilek.cpp
#include "header.hpp"
std::vector<std::string> Zelenina::jidlo(1, "mrkev");
 
// celer.cpp
#include "header.hpp"
const int Zelenina::pocetDruhuNaZacatku = Zelenina::jidlo.size();

Potíž je v tom, že pořadí inicializace globálních proměnných v různých kompilačních jednotkách není definováno. Nelze říct, jestli nejdřív proběhne inicializace proměnné jidlo nebo konstanty pocetDruhuNaZacatku. Takže hodnota v konstantě může být 1, ale taky tam může být něco úplně jiného.

Naštěstí existuje jednoduché řešení, a to je ukrýt globální proměnné do funkcí a metod. Globály ukryté ve funkcích se inicializují ve chvíli, kdy se poprvé provede řádek s jejich definicí.

// header.hpp
struct Zelenina {
    static std::vector<std::string>& jidlo();
    static const int pocetDruhuNaZacatku;
}
 
// lilek.cpp
#include "header.hpp"
std::vector<std::string>& Zelenina::jidlo() {
    static std::vector<std::string> val(1, "mrkev"); // inicializace val se provede, až program bude na tomto řádku
    return val;
}
 
// celer.cpp
#include "header.hpp"
const int Zelenina::pocetDruhuNaZacatku = Zelenina::jidlo().size();

  • Ukryjte globální proměnnou pocetDruhuNaZacatku do statické metody, podobně jako jsme to udělali s globální proměnnou jidlo. Co se stane, když pocetDruhuNaZacatku zavoláme poprvé až na konci programu?

Řešení

Singleton

Proměnná označená static nám pomůže i správně implementovat návrhový vzor singleton (jedináček).

  • Napište třídu Jedinacek a přidejte do ní statickou metodu instance(), která poskytne jeho instanci.
  • Zakažte nebo skryjte klíčové operace třídy, abyste zajistili, že jediná instance je ta od metody instance().
courses/b6b36pjc/cviceni/cviceni-12.txt · Last modified: 2018/09/30 16:09 by horenmar