21.14 — Przeciążanie operatorów i szablonów funkcji

W lekcji 11.7 -- Szablon funkcji instancja, omawialiśmy, w jaki sposób kompilator będzie używać szablonów funkcji do tworzenia instancji funkcji, które są następnie kompilowane. Zauważyliśmy również, że te funkcje mogą się nie skompilować, jeśli kod w szablonie funkcji spróbuje wykonać jakąś operację, której rzeczywisty typ nie obsługuje (np. dodanie wartości całkowitej 1 do std::string).

W tej lekcji przyjrzymy się kilku przykładom, w których nasze instancje funkcji nie zostaną skompilowane, ponieważ nasze rzeczywiste typy klas nie obsługują tych operatorów, i pokażemy, jak możemy zdefiniować te operatory, aby utworzone instancje funkcji zostały następnie skompilowane.

Operatory, wywołania funkcji i szablony funkcji

Najpierw utwórzmy prostą klasę:

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

i zdefiniuj max szablon:

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x < y) ? y : x;
}

Teraz zobaczmy, co się stanie, gdy spróbujemy wywołać max() z obiektem typu Cents:

#include <iostream>

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x < y) ? y : x;
}

int main()
{
    Cents nickel{ 5 };
    Cents dime{ 10 };

    Cents bigger { max(nickel, dime) };
    std::cout << bigger << " is bigger\n";

    return 0;
}

C++ utworzy instancję szablonu dla max(), która wygląda tak:

template <>
const Cents& max(const Cents& x, const Cents& y)
{
    return (x < y) ? y : x;
}

A następnie spróbuje skompilować tę funkcję. Widzisz problem tutaj w C++? nie ma pojęcia, jak ocenić x < y gdy x i y są typu Cents! W rezultacie spowoduje to błąd kompilacji.

Aby obejść ten problem, po prostu przeciąż operator< dowolną klasę, której chcemy użyć max przez:

#include <iostream>

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }
    
    friend bool operator< (const Cents& c1, const Cents& c2)
    {
        return (c1.m_cents < c2.m_cents);
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x < y) ? y : x;
}

int main()
{
    Cents nickel{ 5 };
    Cents dime { 10 };

    Cents bigger { max(nickel, dime) };
    std::cout << bigger << " is bigger\n";

    return 0;
}

Działa to zgodnie z oczekiwaniami i wypisuje:

10 is bigger

Kolejny przykład

Zróbmy jeszcze jeden przykład szablonu funkcji, który nie działa z powodu braku przeciążonych operatorów.

Następujący szablon funkcji obliczy średnią z liczby obiektów w array:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

int main()
{
    int intArray[] { 5, 3, 2, 1, 4 };
    std::cout << average(intArray, 5) << '\n';

    double doubleArray[] { 3.12, 3.45, 9.23, 6.34 };
    std::cout << average(doubleArray, 4) << '\n';

    return 0;
}

To generuje wartości:

3
5.535

Jak widać, działa świetnie w przypadku typów wbudowanych!

Zobaczmy teraz, co się stanie, gdy wywołamy tę funkcję na naszym Cents klasę:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }
};

int main()
{
    Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

Kompilator wpada w szał i generuje mnóstwo komunikatów o błędach! Pierwszy komunikat o błędzie będzie mniej więcej taki:

error C2679: binary << : no operator found which takes a right-hand operand of type Cents (or there is no acceptable conversion)

Pamiętaj ten average() zwraca a Cents obiekt i próbujemy przesłać strumieniowo ten obiekt do std::cout za pomocą operator<<. Jednak nie zdefiniowaliśmy jeszcze operator<< dla naszej Cents klasy. Zróbmy to:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }
};

int main()
{
    Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

Jeśli skompilujemy ponownie, otrzymamy kolejny błąd:

error C2676: binary += : Cents does not define this operator or a conversion to a type acceptable to the predefined operator

Ten błąd jest w rzeczywistości spowodowany instancją szablonu funkcji utworzoną podczas wywoływania average(const Cents*, int). Pamiętaj o tym, gdy wywołujemy a templated, kompilator „wzorcuje” kopię funkcji, w której parametry typu szablonu (typy zastępcze) zostały zastąpione rzeczywistymi typami w wywołaniu funkcji. Oto instancja szablonu funkcji dla obiektu average() gdy T jest Cents :

template <>
Cents average(const Cents* myArray, int numValues)
{
    Cents sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

Powodem otrzymania komunikatu o błędzie jest następujący wiersz:

        sum += myArray[count];

W tym przypadku sum jest Cents obiekt, ale nie zdefiniowaliśmy operator+= for Cents obiektów. Będziemy musieli to zrobić! zdefiniuj tę funkcję, aby average() móc pracować z Cents. Patrząc w przyszłość, widzimy, że average() również używa operator/=, więc przejdziemy dalej i zdefiniujemy również to:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }

    Cents& operator+= (const Cents &cents)
    {
        m_cents += cents.m_cents;
        return *this;
    }

    Cents& operator/= (int x)
    {
        m_cents /= x;
        return *this;
    }
};

int main()
{
    Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

W końcu nasz kod się skompiluje i uruchomi! Oto wynik:

11 cents

Zauważ, że nie musieliśmy zmodyfikować average() aby działał z obiektami typu Cents Musieliśmy po prostu zdefiniować operatory użyte do implementacji average() dla klasy Cents , a kompilator zajął się resztą!

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
221 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze