Симметричные бинарные операторы 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

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

Другие вопросы по тегам