Miesięczne archiwum: Marzec 2016

Bystre triki w C++ (1): wykorzystanie Reguły Zera do śledzenia wywołań Wielkiej Piątki

Reguła Zera jest szczególnym przypadkiem Reguły Pojedynczej Odpowiedzialności – jeżeli jeden z członków twojej klasy potrzebuje specjalnego traktowania przez Wielką Piątkę (destruktor, operatory przypisania, konstruktory kopiujące i konstruktory przenoszące), to powinien być umieszczony w osobnej klasie, inaczej twoja klasa robi za dużo.

Tradycyjnie gdy chcemy prześledzić gdzie wołany jest operator przypisania, dopisujemy do klasy ten operator, w którym wypisujemy na standardowe wyjście. To podejście ma wady – przy dopisywaniu trzeba sprawdzać czy to nie zmieni działania istniejącej klasy, oraz wymusza sprawdzenie które operatory istnieją a które nie – w przypadku klas które nie mają operatora przypisania, dopisanie takiego operatora może zmienić działanie kodu. Ale z użyciem Reguły Zera kod mógłby wyglądać tak:

class noisy
{
    noisy& operator=(noisy&&) noexcept { std::cout << "operator=(noisy&&)\n"; }
    noisy& operator=(const noisy&) { std::cout << "operator=(const noisy&)\n"; }
    noisy(const noisy&) { std::cout << "noisy(const noisy&)\n"; }
    noisy(noisy&&) noexcept { std::cout << "noisy(noisy&&)\n"; }
    ~noisy() { std::cout << "~noisy()\n"; }
    noisy() { std::cout << "noisy()\n"; }
};

class moja_klasa
{
    noisy n;
    int a;
    std::vector<int> b;
};

Et Voila. Klasę można uogólnić żeby nazwa klasy określonej w parametrze szablonu także była wypisywana.

template<typename T>
class noisy
{
    noisy& operator=(noisy&&) noexcept { std::cout << "operator=(noisy<" << typeid(T).name() << ">&&)\n"; }
    noisy& operator=(const noisy&) { std::cout << "operator=(const noisy<" << typeid(T).name() << ">&)\n"; }
    noisy(const noisy&) { std::cout << "noisy(const noisy<" << typeid(T).name() << ">&)\n"; }
    noisy(noisy&&) noexcept { std::cout << "noisy(noisy<" << typeid(T).name() << ">&&)\n"; }
    ~noisy() { std::cout << "~noisy<" << typeid(T).name() << ">()\n"; }
    noisy() { std::cout << "noisy<" << typeid(T).name() << ">()\n"; }
};

class moja_klasa
{
    noisy<moja_klasa> n;
    int a;
    std::vector<int> b;
};