Симметричные бинарные операторы C ++ разных типов - PullRequest
4 голосов
/ 31 марта 2009

Я изучаю 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 ]

8 голосов
/ 31 марта 2009

Лучший способ - определить (вне одного класса) int operator+ (const A& a, const B& b) и сделать его функцией-другом обоих классов, если необходимо. Кроме того, определите

int operator+(const B& b, const A& a) {return a + b;}

Чтобы сделать его симметричным.

3 голосов
/ 31 марта 2009

Большой риск при таком подходе состоит в том, что люди склонны воспринимать + как симметричный оператор. То, как это написано, это не так (если ваши реализации повторяются).

Как минимум, вы должны перегрузить + как внешний бинарный оператор (не как член), а затем поиграть с перегрузкой несколько раз.

Вы должны быть осторожны, чтобы убедиться, что ничто не становится двусмысленным.

Можете ли вы объяснить, что вы пытаетесь сделать? Я не могу вспомнить многих случаев разных типов, в которых имеет смысл иметь симметричные гетерогенные операторы.

1 голос
/ 01 апреля 2009

Я прочитал в комментарии, что вы планируете использовать добавление векторов и матриц. Возможно, вам следует рассмотреть возможность использования только матриц, где векторы являются одномерными матрицами. Тогда у вас останется только один тип и один набор операторов:

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

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

1 голос
/ 31 марта 2009

Основным аргументом для метода 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";
}
...