Skip to the content.

Vlastné stream operátory << a >>

V časti o textových súboroch sme sa dozvedeli ako otvárať súbory, čítať z nich dáta a zapisovať. Takto sa dajú čítať a zapisovať dáta, ktoré majú v štandarde definovaný operátor << a >>. Pre vlastné typy vieme tiež pridať túto podporu.

Vlastný typ TicTacToe

Najprv si musíme definovať typ, ktorý budeme čítať a zapisovať. Napríklad jednoduchú inštanciu TicTacToe, u nás známou ako piškvorky 3x3.

enum class State
{
    Empty,
    Cross, // X
    Nought, // O
};

struct TicTacToe
{
    State board[3][3] = {};
};

Použili sme enum class na definovanie stavu políčka, buď je prázdne, alebo obsahuje krížik, prípadne krúžok. Samotný stav TicTacTou je potom jednoduchá štruktúra, ktorá obsahuje pole stavov políčok 3x3. = {} na konci spôsobí, že je toto pole inicializované na 0, čo je zhodou okolností State::Empty.

Vkladanie dát do std::ostream

Na podporu výstupu potrebujeme aby kompilátor vedel nájsť správny operátor <<. To urobíme tak, že mimo našu triedu deklarujeme nasledujúci operátor.

std::ostream& operator<<(std::ostream& os, const TicTacToe& rhs);

Ľavá strana operátora bude výstupný stream a pravá konštantná referencia na našu triedu. Výstupom potom bude ľavá strana, aby sa dal operátor reťaziť (ak by sme dali výstup ako void, tak by nefungovalo os << t1 << t2).

Serializovať budeme nasledovne. Deväť znakov za sebou, kde X bude krížik, O krúžok a . bude prázdne políčko.

std::ostream& operator<<(std::ostream& os, const TicTacToe& rhs)
{
    for (const auto& i : rhs.board)
    {
        for (const auto& j : i)
        {
            switch (j)
            {
            case State::Cross:
                os << 'X';
                break;
            case State::Nought:
                os << 'O';
                break;
            case State::Empty:
            default:
                os << '.';
            }
        }
    }
}

Pristup k privátnym častiam

Niekedy v rámci operátora << potrebujeme priamy prístup k členským premenným. To vieme zabezpečiť pomocou kľúčového slova friend.

class Person
{
    /* ... */
private:
    std::string m_name;
    uint32_t m_age;

    friend std::ostream& operator<<(std::ostream&, const Person&);
};

std::ostream& operator<<(std::ostream& os, const Person& rhs)
{
    return os << rhs.m_name << " " << rhs.m_age;
}

Extrakcia dát zo std::istream

Analogicky pre čítanie vlastných typov zo streamu musíme preťažiť operátor >>. Ako výstup musí byť referencia stále na vstupný stream aby sa dal aj operátor >> reťaziť. Druhý parameter bude referencia na náš typ (nie konštantná, tú by sme moc nepomenili).

std::istream& operator>>(std::istream& is, TicTacToe& rhs);

Jediný problém pri získavaní dát je reportovanie chýb. Na to musíme v správnej chvíli nastaviť failbit aby sme použivateľovi funkcie dali vedieť, že je niečo zle.

std::istream& operator>>(std::istream& is, TicTacToe& rhs)
{
    for (size_t i = 0; i < 3; ++i)
    {
        for (size_t j = 0; j < 3; ++j)
        {
            if (char state = '\0'; is >> state)
            {
                switch (state)
                {
                case 'X':
                    rhs.board[i][j] = State::Cross;
                    break;
                case 'O':
                    rhs.board[i][j] = State::Nought;
                    break;
                case '.':
                    rhs.board[i][j] = State::Empty;
                    break;
                default:
                    is.setstate(std::ios::failbit);
                    return is;
                }
            }
            else
            {
                return is;
            }
        }
    }
    // everything OK
    return is;
}

Toto je jednoduchý príklad extrahovania dát, ak by nám nestačilo náš operátor vyskladať s už definovaných operátor >>, tak môžeme ísť o úroveň nižšie a implementovať niečo čomu sa hovorí FormattedInputFunction.