13.17 – Перегрузка операторов и шаблоны функций
В уроке «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 ¢s)
{
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 ¢s)
{
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()
, а компилятор позаботился обо всем остальном!