RPH - blog

Blog programisty

Niezwykły operator ternarny ?: w C++

Opublikowany: Czwartek 19.01.2017
0

Każdy zna ten trójargumentowy operator, który istnieje już w języku C. Operator ?: przyjmuje trzy argumenty, pierwszy z nich to wyrażenie, które ma zwracać wartość logiczną, na podstawie której wybierana jest jedna z dwóch pozostałych argumentów. Jeżeli wartość pierwszego wyrażenia będzie oznaczała true, wtedy zostanie zwrócony pierwszy argument, w przeciwnym przypadku drugi. W praktyce może to wyglądać tak:

#include <iostream>

int get_size()
{
    return 0;
}

int main()
{
    int size = get_size();
    std::cout << (size > 0 ? size : 1) << std::endl;
    return 0;
}

Powyższy przykład po skompilowaniu i uruchomieniu wypisze wartość 1. W przykładzie powyżej operator trójargumentowy jest zastosowany do tego, aby jakaś wartość size nie była zerowa. Można skrócić ten zapis wykorzystując fakt, że wartość całkowitoliczbowa jest niejawnie rzutowana na wartość logiczną, w ten sposób, że 0 oznacza false, a pozostałe wartości true. Zatem krócej wygląda to tak:

std::cout << (size ? size : 1) << std::endl;

I teraz bardzo mało znany fakt, że operator trójargumentowy ?: może być operatorem dwuargumentowym! Zatem tą linijkę wypisującą wartość size można jeszcze skrócić:

std::cout << (size ?: 1) << std::endl;

Powyższa konstrukcja zadziała jak poprzednie, czyli jeżeli wartość size będzie wynosić 0 wtedy wypisane zostanie 1, w przeciwnym przypadku wartość size. Co więcej możemy jeszcze skrócić cały kod, nie ma potrzeby, aby przechowywać wartość size w zmiennej.

int main()
{   
    std::cout << (get_size() ?: 1) << std::endl;
}

Powyższy program będzie wykonywał to co poprzednie, ale nie będzie wykorzystywał dodatkowej zmiennej na przechowywanie wartości. Można iść dalej...

Operator ?: może również działać na wskaźnikach, co bardzo skraca zapis sprawdzania czy wskaźnik wskazuje na obiekt czy nie i gdy wskaźnik posiada wartość nullptr utworzyć nowy obiekt. Przykład.

#include <iostream>
#include <string>
#include <memory>

class Object
{
    public:
        Object(const std::string& name = "Default") :
            m_name(name)
        {
        }

        void do_something() const
        {
            std::cout << "Doing something " << m_name << std::endl;
        }
    private:
        std::string m_name;
};

Object* get_object()
{
    // something goes wrong...
    return nullptr;
}

Object* create_object()
{
    return new Object("Scared");
}

int main()
{   
    Object* obj = create_object() ?: new Object();
    obj->do_something();
    delete obj;

    obj = get_object() ?: new Object();
    obj->do_something();
    delete obj;

    return 0;
}

W powyższym przykładzie można krótko sprawdzić, czy funkcja zwróciła wartość nullptr, a gdy tak to utworzyć nowy domyślny obiekt. Poniższy kod po skompilowaniu i uruchomieniu wyświetli następujący tekst:

Doing something Scared
Doing something Default

Operator ?: można stosować nawet z smart pointerami! Ale tu już trzeba uważać - jako wyrażenie warunkowe nie można wywołać konstruktora ani funkcji. Dlatego należy skorzystać ze zmiennej. Przykład poniżej.

std::shared_ptr<Object> create_object_shared_ptr()
{
    return std::make_shared<Object>("Productive");
}

int main()
{
    std::shared_ptr<Object> obj = create_object_shared_ptr();
    obj = obj ?: std::make_shared<Object>();
    obj->do_something();

    return 0;
}

Aby zapisać podobny przykład w jednej linijce razem z deklaracją zmiennej należy wykorzystać funkcję pomocniczą. Przykład poniżej opiera się na std::unique_ptr.

template<typename T>
T&& value_or_default(T&& value, T&& def)
{
    return std::forward<T>(value ?: def);
}

int main()
{
    std::unique_ptr<Object> obj = value_or_default(create_object_ptr(), std::make_unique<Object>());
    obj->do_something();

    return 0;
}

Zastosowanie dodatkowej funkcji już nie jest tak ciekawe, ale pozwala na obejście sytuacji gdzie obiekt musi być zainicjalizowany podczas deklaracji.

Operator dwuargumentowy ?: może działać również na referencjach! Podobnie jak przy smart pointerach w operatorze wyrażenie warunkowe nie może być wywołaniem funkcji albo konstruktora. Przykład poniżej obrazuje sytuację, w której nie udało się otworzyć pliku do zapisu i wypisuje wartości na standardowe wyjście. Ale gdy plik uda się otworzyć, to on będzie wykorzystany.

#include <iostream>
#include <fstream>
#include <string>

void print_to_stream(std::ostream& stream)
{
    stream << "Hello World\n";
}

int main()
{
    std::ofstream file("file.txt");
    print_to_stream(file ?: std::cout);

    return 0;
}

Dwuargumentowy operator ?: działa bardzo podobnie jak operator ?? w języku C#, ma jednak ograniczenia, które nie zawsze pozwalają na jego elastyczne zastosowanie. Operator ten pochodzi z języka C i spełnia tam takie samo zadanie. Mam nadzieję że kogoś zainspirowałem :)


Nie ma jeszcze komentarzy

Zostaw komentarz