13.17 – Перегрузка операторов и шаблоны функций

Добавлено 25 июля 2021 в 00:05
Глава 13 – Перегрузка операторов  (содержание)

В уроке «8.14 – Создание экземпляра шаблона функции» мы обсудили, как компилятор использует шаблоны функций для создания экземпляров функций, которые затем компилируются. Мы также отметили, что эти функции могут не компилироваться, если код в шаблоне функции пытается выполнить какую-либо операцию, которую фактический тип не поддерживает (например, добавление целочисленного значения 1 к std::string).

В этом уроке мы рассмотрим несколько примеров, в которых наши экземпляры функций не будут компилироваться, потому что наши фактические типы классов не поддерживают операторов, и покажем, как мы можем определить эти операторы, чтобы созданные экземпляры функции затем компилировались.

Операторы, вызовы функций и шаблоны функций

Во-первых, давайте создадим простой класс:

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

и определим шаблон функции max:

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

Теперь посмотрим, что происходит, когда мы пытаемся вызвать max() с объектом типа Cents:

class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents(cents)
    {
    }
};
 
template <typename T>
T max(T x, T y)
{
    return (x > y) ? x : y;
}
 
int main()
{
    Cents nickle { 5 };
    Cents dime { 10 };
 
    Cents bigger = max(nickle, dime);
 
    return 0;
}

C++ создаст экземпляр шаблона для max(), который выглядит следующим образом:

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

И затем он попытается скомпилировать эту функцию. Видите здесь проблему? C++ не знает, как вычислить x > y, когда x и y имеют тип Cents! Следовательно, это приведет к ошибке компиляции.

Чтобы обойти эту проблему, просто перегрузите operator> для любого класса, с которым хотите использовать max:

class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents(cents)
    {
    }
    
    friend bool operator>(Cents &c1, Cents&c2)
    {
        return (c1.m_cents > c2.m_cents) ? true: false;
    }
};
 
template <typename T>
T max(T x, T y)
{
    return (x > y) ? x : y;
}
 
int main()
{
    Cents nickle { 5 };
    Cents dime { 10 };
 
    Cents bigger = max(nickle, dime);
 
    return 0;
}

Теперь код работает, как и ожидалось.

Еще один пример

Давайте рассмотрим еще один пример, когда шаблон функции не работает из-за отсутствия перегруженных операторов.

Следующий шаблон функции вычисляет среднее значение из объектов в массиве:

#include <iostream>
 
template <class T>
T average(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;
}

Этот код дает следующий результат:

3
5.535

Как видите, он отлично работает со встроенными типами!

Теперь посмотрим, что происходит, когда мы вызываем эту функцию для объектов нашего класса Cents:

#include <iostream>
 
template <class T>
T average(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 bool operator>(Cents &c1, Cents&c2)
    {
        return (c1.m_cents > c2.m_cents) ? true: false;
    }
};
 
int main()
{
    Cents centsArray[] = { Cents(5), Cents(10), Cents(15), Cents(14) };
    std::cout << average(centsArray, 4) << '\n';
 
    return 0;
}

Компилятор приходит в бешенство и выдает кучу сообщений об ошибках! Первое сообщение об ошибке будет примерно таким:

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

Помните, что average() возвращает объект Cents, и мы пытаемся передать этот объект в std::cout с помощью operator<<. Однако мы еще не определили operator<< для нашего класса Cents. Давайте сделаем это:

#include <iostream>
 
template <class T>
T average(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 bool operator>(Cents &c1, Cents&c2)
    {
        return (c1.m_cents > c2.m_cents) ? true: false;
    }
 
    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;
}

Если мы снова попытаемся скомпилировать код, то получим еще одну ошибку:

c:test.cpp(14) : error C2676: binary '+=' : 'Cents' does not define this operator or a conversion to a type acceptable to the predefined operator

Эта ошибка на самом деле вызвана экземпляром шаблона функции, созданным при вызове average(Cents*, int). Помните, что когда мы вызываем шаблонную функцию, компилятор «набирает» копию функции, в которой параметры шаблонных типов (типы-заполнители) были заменены фактическими типами в вызове функции. Вот экземпляр шаблона функции для average(), когда T является объектом Cents:

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

Причина, по которой мы получаем сообщение об ошибке, заключается в следующей строке:

sum += myArray[count];

В этом случае sum является объектом Cents, но мы не определили operator+= для объектов Cents! Нам нужно будет определить эту функцию, чтобы функция average() могла работать с Cents. Заглядывая вперед, мы видим, что в average() также используется operator/=, поэтому мы продолжим и определим и его:

#include <iostream>
 
template <class T>
T average(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 bool operator>(Cents &c1, Cents&c2)
    {
        return (c1.m_cents > c2.m_cents) ? true: false;
    }
 
    friend std::ostream& operator<< (std::ostream &out, const Cents &cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }
 
    Cents& operator+=(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;
}

Наш код, наконец, компилируется и запускается! Вот результат:

11 cents

Обратите внимание, что нам вообще не нужно было изменять average(), чтобы она работала с объектами типа Cents. Нам просто нужно было определить для класса Cents операторы, используемые для реализации average(), а компилятор позаботился обо всем остальном!

Теги

C++ / CppLearnCppДля начинающихОбучениеОператор (программирование)Перегрузка (программирование)Перегрузка операторовПрограммированиеШаблон / TemplateШаблон функции

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.