Přeskočit na obsah

Reference (C++)

Z Wikipedie, otevřené encyklopedie

Reference v programovacím jazyce C++ je jednoduchý referenční datový typ, který poskytuje část funkčnosti typu ukazatel z jazyka C, ale je bezpečnější. Název reference může způsobovat nedorozumění, protože v matematické informatice je reference obecný koncept datového typu, jehož konkrétní implementací je ukazatel i zde popisovaná C++ reference. Definice reference v C++ je taková, že nemusí existovat. Může být implementována jako nové jméno pro existující objekt[pozn. 1] (podobně jako klíčové slovo rename v jazyce Ada).

Syntaxe a terminologie

[editovat | editovat zdroj]

Lvalue reference

[editovat | editovat zdroj]

Deklarace tvaru:

typ& identifikátor

deklaruje identifikátor typu lvalue reference na zadaný typ.[1]

Příklady:

int a = 5;
int& r_a = a;

extern int& r_b;

r_a a r_b jsou typu „lvalue reference na int

int& Foo();

Foo je funkce, která vrací „lvalue referenci na int“;

void Bar(int& r_p);

Bar je funkce, jejímž parametrem je „lvalue reference na int“;

class MyClass { int& m_b; /* ... */ };

MyClass je třída s členem m_b, který je lvalue referencí na int;

int FuncX() { return 42 ; };
int (&f_func)() = FuncX;
int (&&f_func2)() = FuncX; // v zásadě totéž jako předchozí

FuncX je funkce, která vrací (nereferenční typ) int a f_func je alias pro FuncX

const int& ref = 65;

const int& ref je lvalue reference na const int ukazující na paměť obsahující hodnotu 65.

int arr[3];
int (&arr_lvr)[3] = arr;
int (&&arr_rvr)[3] = std::move(arr);
typedef int arr_t[3];
int (&&arr_prvl)[3] = arr_t{}; // arr_t{} je prvalue typu pole
int *const & ptr_clv = arr; // totéž jako int *const & ptr_clv = &arr[0];
int *&& ptr_rv = arr;
// int *&arr_lv = arr; // Chyba: inicializace lvalue reference na nekonstantní typ pomocí rvalue

arr_lvr je reference na pole. Při inicializaci reference na pole se neprovede konverze pole na ukazatel, provede se však při inicializaci reference na ukazatel. Protože konverze pole na ukazatel vrátí prvalue, jeho výsledkem lze inicializovat pouze lvalue reference na const nebo rvalue reference. Podobně při inicializaci reference na funkce, se neprovede konverze funkce na ukazatel (viz f_func výše), ale při inicializaci reference na ukazatel na funkci ano:

int FuncX() { return 42 ; };
int (*const &pf_func)() = FuncX; // totéž jako int (*const &pf_func)() = &FuncX;
int (* &&pf_func2)() = FuncX;

Rvalue reference

[editovat | editovat zdroj]

Deklarace tvaru:

typ&& identifikátor

deklaruje identifikátor typu rvalue reference na zadaný typ. Protože jméno rvalue reference je samo o sobě lvalue, je třeba použít std::move pro předání rvalue reference na přetížení funkce, jehož parametrem je rvalue reference. Rvalue se odkazuje na cv-nekvalifikovaný parametry šablony typ stejné šablony funkce nebo na auto&& kromě případu, kdy je odvozena ze seznamu inicializátorů uzavřených ve složených závorkách, nazýváme forwardující referencí (dříve též „univerzální reference“[2]) a může fungovat jako lvalue nebo rvalue reference podle toho, jaký dostanou parametr.[3] V parametrech funkce se někdy používá std::forward pro předání argumentu funkce jiné funkci, přičemž se zachovává, zda jde o lvalue nebo rvalue.[4]

Typy, které jsou referencemi na nějaký typ, se někdy nazývají referenční typy. Identifikátory, které jsou referenčního typu se nazývají referenční proměnné, přestože to není příliš vhodné pojmenování, jak uvidíme dále.

Význam a omezení

[editovat | editovat zdroj]

Reference nejsou objekty a mohou se odkazovat pouze na objekty nebo funkce. Nemůže existovat pole referencí, protože pole se musí skládat z objektů. Stejně tak nemohou existovat ukazatele na referenci a reference na referenci, protože ukazatel musí ukazovat na objekt a reference se musí odkazovat na objekt. To znamená, že int& i[4], int&*i a int& &i způsobí chybu překladu (zatímco int(& i)[4] (reference na pole) a int*&i (reference na ukazatel) chybou nejsou, pokud jsou inicializované). Také reference na void je chybná, protože void není typ, ale vyjádření absence typu; reference na void * může existovat – je to reference na ukazatel nespecifikovaného typu.

Reference nemohou být const nebo volatile (např. int& volatile i selže, prošlo by to pouze v typedef/decltype, ale pak by const/volatile bylo ignorováno). Pokud při vyvozování argumentu šablony je vyvozen typ reference (což se stane při použití forwardujících referencí a předávání lvalue funkci) nebo pokud typedef, using nebo decltype popisují referenci, pak je možné vytvořit referenci na takový typ. V tomto případě se pro určení typu reference používá pravidlo, které se nazývá kolaps reference a funguje takto: Je-li T typ a TR typ reference na T, pak rvalue reference na TR bude opět typ TR, zatímco lvalue reference na TR bude lvalue reference na T. Jinými slovy lvalue reference mají přednost před rvalue referencemi a rvalue reference na rvalue reference zůstávají nezměněné.

int i;
typedef int& LRI;
using RRI = int&&;

LRI& r1 = i; // r1 je typu int&
const LRI& r2 = i; // r2 je typu int&
const LRI&& r3 = i; // r3 je typu int&

RRI& r4 = i; // r4 je typu int&
RRI&& r5 = 5; // r5 je typu int&&

decltype(r2)& r6 = i; // r6 je typu int&
decltype(r2)&& r7 = i; // r7 je typu int&

Nestatickou členskou funkci lze deklarovat s kvalifikátorem ref. Tento kvalifikátor se podílí na výběru varianty přetížené funkce a uplatní se na implicitní parametr objektu jako const nebo volatile, ale na rozdíl od nich nemění vlastnosti this. Jeho významem je, že nařizuje, aby byla funkce volána na lvalue nebo rvalue instanci třídy.

#include <iostream>

struct A
{
  A() = default;
  void Print()const& { std::cout << "lvalue\n"; }
  void Print()const&& { std::cout << "rvalue\n"; }
};

int main()
{
    A a;
    a.Print();            // vypíše "lvalue"
    std::move(a).Print(); // vypíše "rvalue"
    A().Print();          // vypíše "rvalue"
    A&& b = std::move(a);
    b.Print();            // vypíše "lvalue"(!)
}

Vztah k ukazatelům

[editovat | editovat zdroj]

Reference v C++ se v několika ohledech liší od ukazatelů:

  • Samotná reference není objekt, ale přezdívka (alias); použití jména reference se odkazuje přímo na objekt, na nějž je referencí. Deklarace ukazatele vytváří objekt ukazatel, který není totéž jako objekt, na který se odkazuje.
    • Reference nemohou být součástí větších struktur (kontejnerů), protože reference nejsou objekty, zatímco ukazatele mohou být součástí struktur nebo kontejnerů (ukazatel v kontejneru je běžná metoda, jak dosáhnout polymorfismu).
    • Není možné vytvářet reference na reference, protože reference se mohou odkazovat pouze na objekty (nebo funkce).
  • Reference nemohou být neinicializované. Protože je nemožné reinicializovat reference, musejí být inicializované hned při vytváření. Jednou vytvořená reference nemůže být později změněna, aby se dokazovala na jiný objekt. S ukazateli to možné je.
  • Na rozdíl od ukazatelů nemohou mít reference hodnotu null; každá reference se odkazuje na nějaký objekt, i když nemusí být platná.

Reference, které jsou samostatnými lokálními nebo globálními proměnnými, musejí být inicializovány ve své definici; a reference, které jsou datovými členy instance třídy, musejí být inicializovány v seznamu inicializátorů konstruktoru třídy. Například:

  • int& k; // způsobí chybu při překladu: error: `k' declared as reference but not initialized
    

Mezi ukazateli a referencemi existuje jednoduchý převod: operátor & použitý na referenci vrací ukazatel na stejný objekt, a naopak pokud se reference inicializuje dereferencí (*) hodnoty ukazatele, bude se odkazovat na stejný objekt jako tento ukazatel, pokud je možné bez vyvolání nedefinovaného chování. Tato ekvivalence odráží typickou implementaci, která efektivně realizuje reference jako ukazatele, které jsou při každém použití implicitně dereferencovány. I když překladače obvykle realizují reference pomocí ukazatelů, C++ norma to nevyžaduje.

To má za následek, že v mnoha implementacích práce s proměnnými s automatickou nebo statickou životností pomocí referencí může způsobovat skryté operace dereference, které, i když se syntakticky podobají přímému přístupu, jsou nákladné.

Protože operace s referencemi jsou tak omezené, je mnohem snazší rozumět referencím než ukazatelům a použití referencí je také odolnější proti chybám. Zatímco ukazatel je možné zneplatnit mnoha mechanismy, od použití hodnoty null přes použití aritmetiky ukazatelů k opuštění mezí pole po nepovolené změny typů při jejich vytváření z libovolného celého čísla; dříve platná reference se stane neplatnou pouze ve dvou případech:

  • Pokud se odkazuje na objekt s automatickým přidělováním, a provádění programu opustí oblast platnosti tohoto objektu,
  • Pokud se odkazuje na objekt v dynamicky alokované paměti, která byla uvolněna.

První případ lze snadno automaticky detekovat, pokud má reference statický rozsah platnosti, ale představuje to problém, pokud reference je členem dynamicky alokovaného objektu; druhý případ se detekuje obtížněji. To jsou jediné problémy s referencemi, které lze řešit rozumnou strategií přidělování paměti.

Použití referencí

[editovat | editovat zdroj]

Nejobvyklejším použitím referencí je pro (formální) parametry funkcí. Reference umožňují číst i měnit hodnoty argumentů (skutečných parametrů) funkcí, aniž by bylo třeba při každém použití parametru použít operátor dereference *.

  • Jiné než jako náhrada za ukazatele, jedním příhodným použitím referencí je v seznamech parametrů funkcí, kde umožňují předávání parametrů používaných pro výstup bez explicitní zjišťování adresy volajícím. Například:
void Square(int x, int& out_result) {
  out_result = x * x;
}

Vyvolání této funkce s argumenty 3, y uloží do proměnné y druhou mocninu čísla 3:

int y;
Square(3, y);

Pokus vyvolat tuto funkci s celočíselným literálem jako druhým argumentem způsobí chybu překladu, protože parametry, které jsou lvalue referencemi bez const mohou být vázány pouze na adresovatelné hodnoty:

Square(3, 6);
  • Jestliže funkce vrací lvalue referenci, je možné použít volání funkce na levé straně přiřazovacího příkazu:
    int& Preinc(int& x) {
      return ++x;  // "return x++;" je špatně
    }
    
    Preinc(y) = 5;  // totéž jako ++y, y = 5
    
  • V mnoha implementacích normální mechanismy předávání parametrů často vyžadují nákladné operace kopírování velkých parametrů. Reference s const jsou užitečným způsobem předávání velkých objektů mezi funkcemi, který se této režii vyhýbá:
    void FSlow(BigObject x) { /* ... */ }
    void FFast(const BigObject& x) { /* ... */ }
    
    BigObject y;
    
    FSlow(y);  // Pomalé, kopíruje y do parametru x.
    FFast(y);  // Rychlé, poskytuje přímý přístup k y (pouze pro čtení).
    

Pokud by funkce FFast skutečně vyžadovala vlastní kopii x, aby ji mohla měnit, musí si kopii vytvořit explicitně. Stejnou techniku lze použít i s ukazateli, u kterých je však nutné ke všem použitím parametru doplnit operátor (&) k získání adresy, a stejně obtížné by bylo tuto změnu zrušit, pokud by se objekty později zmenšily.

Polymorfní chování

[editovat | editovat zdroj]

Při srovnání referencí a ukazatelů (v jazyce C++), mají reference polymorfní funkcionalitu:

#include <iostream>

class A {
 public:
  A() = default;
  virtual void Print() { std::cout << "This is class A\n"; }
};

class B : public A {
 public:
  B() = default;
  virtual void Print() { std::cout << "This is class B\n"; }
};

int main() {
  A a;
  A& ref_to_a = a;

  B b;
  A& ref_to_b = b;

  ref_to_a.Print();
  ref_to_b.Print();
}

Tento program vypíše:

This is class A

This is class B
  1. Slovo objekt je v tomto článku používáno ve významu „samostatná datová entita“, nikoli v užším v objektově orientovaném programování používaném významu entity, která kromě datové části obsahuje i operace (metody), pomocí nichž lze s objektem pracovat.

V tomto článku byl použit překlad textu z článku Reference (C++) na anglické Wikipedii.

  1. ISO/IEC 14822, clause 9.3.3.2, paragraph 1.
  2. SUTTER, Herb; STROUSTRUP, Bjarne; DOS REIS, Gabriel. Forwarding References [online]. Dostupné online. 
  3. ISO/IEC 14822, clause 13.10.2.1, paragraph 3.
  4. BECKER, Thomas. C++ Rvalue References Explained [online]. [cit. 2022-11-25]. Dostupné online. 

Externí odkazy

[editovat | editovat zdroj]