na obsah klávesové zkratky na hlavní stránku na menu

Třídy

Jakožto objektový jazyk musí mít jazyk C++ určitý koncept objektů. Ten tvoří třídy, které byly největší novinkou přidanou do C++. V tomto článku se podíváme na základní vlastnosti tříd.

Motivace

Koncept objektového programování vychází z myšlenky izolace funkčních bloků a jejich následného spojování. Podobnou izolaci jsme mohli potkat již v jazyce C, kde jsme používali funkce k vykonávání určitého pevně ohraničeného postupu. U větších programů je ale granularita funkcí příliš jemná, nehledě na to, že funkce v sobě neuchovávají stavy. Ty je nutné dodat jako parametr. Proto vznikl koncept tříd, kdy je možné vzít několik datových členů a funkcí a přiřadit jim společný kontejner.

Deklarace třídy

Jak už bylo řečeno výše, je nutné nějak zajistit sdružení funkcí a proměnných. V jazyce C existoval způsob, jakým šlo spojit vícero heterogenních dat do jednoho bloku — struktury. Jazyk C++ tuto myšlenku převzal a lehce ji rozšířil o možnost přidat do struktury i funkce. Navíc vzniklo nové klíčové slovo class, které vytvoří třídu místo struktury. Z toho je zřejmé, že struktury a třídy mají v jazyce C++ mnoho společného. Liší se prakticky pouze jedním detailem, který bude zmíněn později.

Třídu vytvoříme podle následujícího vzoru:

class <jméno>:<[seznam předků]>
{
  <[oprávnění]>
  <seznam členů>
}<[deklarace]>;

Tento zápis je poměrně komplikovaný. Vysvětlíme si proto jeho jednotlivé části. Začínáme zápisem slova class. Zde bychom mohli použít i slovo struct a vytvořili bychom strukturu (již víme, že v C++ není potřeba provádět hrátky s typedef). Následuje povinné jméno třídy a za dvojtečkou nepovinný seznam předků třídy (viz dědičnost). Uvnitř bloku složených závorek se nachází vlastní tělo třídy. Zde můžeme deklarovat proměnné a funkce. Členským proměnným třídy se říká datové členy, zatímco funkcím se v kontextu tříd říká metody. V seznamu členů se ještě mohou vyskytnou návěští oprávnění, která nabývají jednoho ze tří hodnot public, private a protected. Za ukončovací složenou závorkou může a nemusí být seznam deklarací třídy (podobně jako v jazyce C u struktur).

Nyní bude nejlepší si vytvořit jednoduchou třídu. Uvažme třídu Vozidlo.

class Vozidlo
{
  private:
  int pocetKol;
  float maxRychlost;
  public:
  float getMaxRychlost();
  int getPocetKol();
} kolo;

// ...
Vozidlo trabant;

Vytvořili jsme třídu Vozidlo, která obsahuje dva datové členy a dvě metody. Datové členy jsou soukromé a metody jsou veřejné (význam private a public viz níže). Nakonec vytvoříme tzv. instanci kolo a trabant (Stejně jako u struktur v jazyce C je možné vytvářet instance ihned za deklarací třídy před středníkem nebo pak v kódu jako obyčejnou proměnnou). Instance je konkrétní podoba třídy, je to proměnná s typem předepsaným v deklaraci třídy. Vlastní deklarace třídy je pouze předpis struktury třídy, nicméně až teprve instance vytvořená podle tohoto předpisu obsahuje konkrétní data a lze volat její metody. V dalším kódu programu bychom metodu getPocetKol() instance kolo zavolali následovně:

// ...
// používá se tečková notace jako u struktur
int i = kolo.getPocetKol();

Deklarace vs definice

Předchozí volání metody má jeden nedostatek, a to poměrně významný. Nikde totiž neříkáme, jak vypadá tělo metody getPocetKol(). V deklaraci třídy pouze předepisujeme, že třída tuto metodu bude obsahovat, ale její tělo musíme uvést „jinde”. Jsou celkem dvě možnosti — umístit definici do těla třídy, nebo mimo něj. První případ je úspornější a hodí se pro jednoduché metody typu getter/setter. Kód by vypadal následovně:

class Vozidlo
{
  private:
  int pocetKol;
  float maxRychlost;
  public:
  float getMaxRychlost();
  // za závorkou není středník, jde o definici funkce
  int getPocetKol()
  {
    return pocetKol;
  }
} kolo;

Všimněme si, že v těle metody můžeme bez problémů používat datové členy třídy, aniž bychom to překladači museli nějak obšírněji vysvětlovat. Metoda patří ke třídě, čili má automaticky nárok na všechny datové členy. Není třeba se na ně dotazovat jakýmkoliv speciálním způsobem.

Druhý způsob spočívá v umístění těla metody mimo třídu. To se používá převážně u delších metod, kdy je výhodné umístit kód do zdrojového souboru a v hlavičkovém souboru ponechat pouze deklaraci třídy.

// soubor vozidlo.h

class Vozidlo
{
  private:
  int pocetKol;
  float maxRychlost;
  public:
  float getMaxRychlost();
  int getPocetKol();
} kolo;

// soubor vozidlo.cpp

#include "vozidlo.h"

int Vozidlo::getPocetKol()
{
  return pocetKol;
}

V tomto případě musíme definici svázat se třídou pomocí operátoru čtyřtečka. Je totiž možné, že budeme mít víc tříd se stejným názvem metody a překladač by pak nevěděl, kterou kam přiřadit. Kromě kvalifikace jménem třídy už se kód nijak neliší od předchozího. Opět můžeme využít datové členy třídy, jejich dostupnost zajistila právě kvalifikace. Kvalifikace se vždy umisťuje před jméno dané metody. Pokud kvalifikaci zapomeneme uvést, bude překladač považovat kód za definici obyčejné funkce, která nebude mít přístup k žádnému ze členů třídy. Taková chyba se při překladu v G++ projevuje následujícím hlášením.

error: ‘pocetKol’ was not declared in this scope

Ukazatel this

Podívejme se nyní na volání metod z pohledu jazyka C, kde žádné třídy neexistovaly. kdybychom chtěli chování tříd simulovat, stačilo by do funkcí, které by odpovídaly metodám, vložit ještě jeden parametr, který by určoval instanci, nad kterou byla funkce v tu chvíli spuštěna. Jazyk C++ ve skutečnosti nedělá nic jiného, jen tento fakt vcelku úspěšně skrývá. Každá metoda má totiž oproti její deklaraci jeden parametr navíc, který je vždy prvním zleva. Tento parametr je konstantní ukazatel s pevným jménem this a ukazuje na instanci, ze které byla metoda volána. Čili při volání metody getPocetKol() v příkladu výše by v těle metody byl k dispozici parametr this, který by ukazoval na instanci kolo.

Přístupová práva

Objektové programování stojí na třech základních pilířích. Jsou jimi zapouzdření, polymorfismus a dědičnost. Nyní se zmíníme jen o tom prvním. Zapouzdření je vcelku logický požadavek na objekty. Ty by totiž měly existovat samostatně bez závislosti na externích datech a přístup dovnitř objektu by měl být hlídaný. Toto dělení zajistí robustnost našeho návrhu, protože při správně napsaném kódu bude každá třída nezávislým funkčním celkem, který půjde univerzálně použít nebo třeba celý nahradit bez nutnosti přepisovat celý kód.

V jazyce C++ slouží k dosažení cíle zapouzdření obor viditelnosti a přístupová práva. Každá třída je oborem viditelnosti, tak, jak tomu bylo u funkcí v jazyce C. To znamená, že všechny datové členy mají platnost jen a pouze v rámci dané třídy a nezasahují tak do vnějšího světa. Přístupová práva naopak řeší omezení přístupu k interním datům třídy z vnějšku. Jistě nechceme, aby si kdokoliv mohl vzít naši pracně vytvořenou třídu a přepsat např. uložený plat na záporné číslo. Proto máme celkem tři úrovně oprávnění pro přístup ke členům třídy. Už jsme se s nimi setkali výše, a tudíž víme, jak se zapisují. Platí pravidlo, že oprávnění se vztahuje na všechny deklarace uvedené pod ním. Tudíž v naší třídě se oprávnění private aplikuje na oba datové členy, ale všechny metody už jsou pod vlivem oprávnění public. Nyní k jednotlivým významům.

private
Klíčové slovo private znamená, že blok ovlivněných deklarací bude interním vlastnictvím třídy. Nikdo kromě třídy samotné nebude mít k datům a metodám přístup. Vedlejším efektem je to, že když vytvoříme privátní konstruktor, a to můžeme, tak v podstatě nepůjde vytvořit instance třídy.
public
Klíčové slovo public znamená, že blok ovlivněných deklarací bude veřejný. Kdokoliv, ať už třída nebo okolí, bude moct k datům a metodám přistupovat. V drtivé většině případů bývá konstruktor deklarovaný jako veřejný. Také všechny metody getter/setter bývají zpravidla veřejné.
protected
Klíčové slovo protected znamená, že blok ovlivněných deklarací bude interním vlastnictvím třídy a jejích potomků. Toto oprávnění má vztah ke třetímu pilíři — dědičnosti — a bude vysvětleno později.

Nyní už máme dostatek znalostí, abychom vysvětlili jediný rozdíl mezi třídou a strukturou v jazyce C++. Jde o implicitní oprávnění, tedy to, které se uvažuje, pokud žádné oprávnění nespecifikujeme. U struktur je to oprávnění public, kdežto u tříd je to private. Struktura tak implicitně umožňuje přístup ke všem svým členům, přesně, jak jsme zvyklí z jazyka C, zatímco u třídy je v základu všechno privátní a tudíž nedostupné.