Основные правила и идиомы перегрузки операторов в C++
Основные правила перегрузки операторов
Ваш пример типичен для начинающих. Давайте разберем все проблемы по порядку.
1. Проблема с 5.0 + a
Когда вы пишете a + 5.0, компилятор видит Complex::operator+(double) и неявно преобразует 5.0 в Complex(5.0, 0.0) через конструктор с одним параметром. Но при 5.0 + a левый операнд — double, у которого нет метода operator+(Complex). Решение — сделать оператор свободной функцией:
// Дружественная функция вне класса
Complex operator+(const Complex& lhs, const Complex& rhs) {
return Complex(lhs.real + rhs.real, lhs.imag + rhs.imag);
}
Тогда 5.0 + a сработает, так как 5.0 будет преобразован в Complex через конструктор.
2. Идиома возврата по значению и константность
Ваш operator+ должен:
- Принимать
const Complex&(а не копию) — избегаем лишнего копирования - Быть
constдля метода класса или свободной функцией - Возвращать новый объект, а не ссылку (так как результат — временное значение)
class Complex {
public:
// Правильная перегрузка как метод
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
};
3. Перегрузка operator==
Ваш вариант не константный, что неправильно. Сравнение не должно изменять объект. Правильно:
bool operator==(const Complex& other) const {
return (real == other.real && imag == other.imag);
}
Также часто делают свободную функцию для симметричности:
bool operator==(const Complex& lhs, const Complex& rhs) {
return (lhs.real == rhs.real && lhs.imag == rhs.imag);
}
4. Метод класса vs свободная функция
Золотое правило:
- Операторы, которые изменяют объект (
+=,-=,++,--) — делайте методами класса - Бинарные операторы, создающие новые объекты (
+,-,*,/) — свободные функции (для симметрии левого и правого операнда) - Операторы сравнения (
==,!=,<) — свободные функции или константные методы - Операторы ввода/вывода (
<<,>>) — только свободные функции
5. Идиома копирования и обмена (Copy-and-Swap)
Для операторов присваивания с составным действием (+=):
Complex& operator+=(const Complex& other) {
real += other.real;
imag += other.imag;
return *this;
}
// operator+ использует operator+=
Complex operator+(const Complex& lhs, const Complex& rhs) {
Complex result = lhs;
result += rhs;
return result;
}
Полный правильный пример
class Complex {
public:
double real, imag;
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
// Составное присваивание
Complex& operator+=(const Complex& other) {
real += other.real;
imag += other.imag;
return *this;
}
// Унарный минус
Complex operator-() const {
return Complex(-real, -imag);
}
// Префиксный инкремент
Complex& operator++() {
++real;
return *this;
}
// Постфиксный инкремент (фиктивный int параметр)
Complex operator++(int) {
Complex temp = *this;
++(*this);
return temp;
}
};
// Свободные функции для бинарных операторов
Complex operator+(const Complex& lhs, const Complex& rhs) {
Complex result = lhs;
result += rhs;
return result;
}
bool operator==(const Complex& lhs, const Complex& rhs) {
return (lhs.real == rhs.real && lhs.imag == rhs.imag);
}
bool operator!=(const Complex& lhs, const Complex& rhs) {
return !(lhs == rhs);
}
ostream& operator<<(ostream& os, const Complex& c) {
os << "(" << c.real << ", " << c.imag << "i)";
return os;
}
Ключевые идиомы:
- DRY (Don't Repeat Yourself) — реализуйте
operator+черезoperator+= - Константная корректность — методы, не изменяющие объект, должны быть
const - Симметрия — бинарные операторы как свободные функции
- Возврат по значению — для операторов, создающих новые объекты
- Возврат ссылки — для операторов присваивания (
*this)
Альтернативный подход: использование CRTP для автоматической генерации операторов
Если вы часто перегружаете операторы для разных классов, можно использовать шаблонный базовый класс (CRTP — Curiously Recurring Template Pattern), чтобы автоматически генерировать operator+ из operator+= и другие комбинации.
template<typename Derived>
class ArithmeticOperators {
public:
friend Derived operator+(const Derived& lhs, const Derived& rhs) {
Derived result = lhs;
result += rhs;
return result;
}
friend Derived operator-(const Derived& lhs, const Derived& rhs) {
Derived result = lhs;
result -= rhs;
return result;
}
};
class Complex : public ArithmeticOperators<Complex> {
public:
double real, imag;
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
Complex& operator+=(const Complex& other) {
real += other.real;
imag += other.imag;
return *this;
}
Complex& operator-=(const Complex& other) {
real -= other.real;
imag -= other.imag;
return *this;
}
};
Этот подход особенно полезен, когда у вас много классов с похожими операторами (например, математические векторы, матрицы). Вы определяете только составные операторы (+=, -=), а бинарные генерируются автоматически. Минус — сложнее отладка и менее очевидный код для новичков.
Совет по производительности
Для тяжелых объектов (например, большие матрицы) рассмотрите семантику перемещения (move semantics) и реализацию через std::move в operator+:
Complex operator+(Complex lhs, const Complex& rhs) {
lhs += rhs;
return lhs; // Компилятор применит move или NRVO
}
Здесь
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги