Симметричные бинарные операторы C++ с различными типами
Я изучаю C++, и мне было интересно, смогу ли я получить представление о предпочтительном способе создания бинарных операторов, которые работают с экземплярами двух разных типов. Вот пример, который я сделал, чтобы проиллюстрировать свои опасения:
class A;
class B;
class A
{
private:
int x;
public:
A(int x);
int getX() const;
int operator + (const B& b);
};
class B
{
private:
int x;
public:
B(int x);
int getX() const;
int operator + (const A& A);
};
A::A(int x) : x(x) {}
int A::getX() const { return x; }
// Method 1
int A::operator + (const B& b) { return getX() + b.getX(); }
B::B(int x) : x(x) {}
int B::getX() const { return x; }
// Method 1
int B::operator + (const A& a) { return getX() + a.getX(); }
// Method 2
int operator + (const A& a, const B& b) { return a.getX() + b.getX(); }
int operator + (const B& b, const A& a) { return a.getX() + b.getX(); }
#include <iostream>
using namespace std;
int main()
{
A a(2);
B b(2);
cout << a + b << endl;
return 0;
};
Если я хотел бы иметь симметрию между двумя типами, какой метод является лучшим подходом в приведенном выше коде. Есть ли возможные опасности при выборе одного метода над другим? Это зависит от типа возвращаемого значения? Пожалуйста, объясни! Спасибо!
4 ответа
Лучший способ - это определить (вне класса) int operator+ (const A& a, const B& b)
и сделайте его функцией-другом обоих классов, если это необходимо. Кроме того, определить
int operator+(const B& b, const A& a) {return a + b;}
Чтобы это было симметрично.
Большой риск при таком подходе состоит в том, что люди склонны воспринимать + как симметричный оператор. То, как это написано, это не так (если ваши реализации не совпадают).
Как минимум, вы должны перегрузить + как внешний бинарный оператор (не как член), а затем поиграть с его перегрузкой несколько раз.
Вы должны быть осторожны, чтобы убедиться, что ничего не становится двусмысленным.
Можете ли вы объяснить, что вы пытаетесь сделать? Я не могу думать о многих случаях разных типов, в которых имеет смысл иметь симметричные гетерогенные операторы.
Основной аргумент метода 2 заключается в том, что вы получаете неявное преобразование типов для обоих операндов, а не только для второго. Это может спасти путаницу где-нибудь в будущем.
Говоря об этом, ваш пример кода определяет неявные преобразования из int в A и из int в B через конструкторы 1-arg в обоих классах. Это может привести к неоднозначности позже. Но если вы опустите "явное" для краткости, достаточно справедливо.
Однако я согласен с предупреждением Ури: если вы обнаружите, что делаете это, возможно, вы пишете API-интерфейс, который другие смущают. Почему A плюс B это int? Неужели пользователям будет проще добавлять a и b вместо того, чтобы сами вызывать getX и добавлять результаты?
Это потому, что пользователи прекрасно знают, что A и B являются обертками для целых? Если это так, то другой вариант - выставить преобразования из A в int и B в int через оператор int(). Тогда a+b вернет int по разумной причине, и вы получите все остальные арифметические операторы:
#include <iostream>
struct A {
int x;
explicit A(int _x) : x(_x) {}
operator int() {
return x;
}
};
struct B {
int x;
explicit B(int _x) : x(_x) {}
operator int() {
return x;
}
};
int main() {
A a(2);
B b(2);
std::cout << a + b << "\n";
std::cout << a - b << "\n";
}
Я прочитал в комментарии, что вы планируете использовать добавление векторов и матриц. Возможно, вам следует рассмотреть возможность использования только матриц, где векторы являются одномерными матрицами. Тогда у вас останется только один тип и один набор операторов:
matrix operator*( matrix const& a, matrix const& b );
matrix operator+( matrix const& a, matrix const& b ); // and so on
Если вы хотите сохранить класс векторов, вам следует подумать, хотите ли вы также транспонировать вектор (возможно, транспонирование - это просто внутреннее свойство вектора).
Набор операций не является действительно симметричным:
vector * matrix = vector matrix * vector_t = vector_t matrix * matrix = matrix vector_t * vector = matrix vector * vector_t = int
и вы должны предложить эти три операции (предполагая, что transpose является свойством вектора):
vector operator*( vector const& v, matrix const& m );
vector operator*( matrix const& m, vector const& v );
matrix operator*( matrix const& m1, matrix const& m2 );
matrix operator*( vector const& v1, vector const& v2 ); // possibly 1x1 matrix, you cannot overload changing only return value
Все как свободные функции, если это возможно. Даже если вышеприведенный набор не является симметричным, реальный мир тоже не будет, и ваши пользователи будут ожидать этого.