Table of Contents

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.

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

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

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á.

Ř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();

Řešení

Singleton

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