2009-03-31 2 views
3

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

Если я хотел бы иметь симметрию между двумя типами, какой метод является наилучшим подходом в приведенном выше коде. Существуют ли какие-либо возможные опасности при выборе одного метода над другим? Это зависит от типа возврата? Пожалуйста, объясни! Спасибо!

+0

Возможно, лучшее описание A и B и то, как они связаны, могут помочь в определении простого простого решения. Связаны ли они? Один конвертируемый в другой? –

ответ

8

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

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

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

+0

Сделать его другом, только если вам нужно. Иногда вы делаете, а иногда нет. –

+0

Хороший момент. Отредактировано в. – rlbond

+0

И надеюсь, что у вас нет неявного способа преобразования между A и B (например, конструкторы не отмечены явно) ... – Uri

3

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

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

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

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

1

Главный аргумент метода 2 заключается в том, что вы получаете неявное преобразование типа для обоих операндов, а не только для второго. Это может сдержать замешательство где-то на линии.

Говоря об этом, ваш пример кода определяет неявные преобразования из int в A и из int в B через конструкторы 1-arg в обоих классах. Это может привести к неоднозначности позже. Но если вы оставили «явный» для краткости, справедливо.

Я согласен с предупреждением Ури: если вы обнаружите, что занимаетесь этим, вы можете написать API, который другие будут сбивать с толку. Почему A плюс a B является int? Разве это действительно облегчает пользователям то, что они добавляют a и b, а не вызывает getX и добавляет результаты?

Это потому, что пользователи прекрасно знают, что A и B являются обертками для ints? Если это так, то другой вариант заключается в том, чтобы вывести преобразования из A в int и B в int через оператор int(). Тогда а + б возвращает 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"; 
} 
+0

В качестве примеров я создал классы A и B. Более практичный пример может включать класс Matrix и Vector, где умножение определяется в обоих направлениях. Один из способов дал бы обычный вектор, а другой мог бы принять, а также вернуть транспонированный вектор. – 2009-03-31 19:44:37

+0

Затем проигнорируйте все после моего первого абзаца :-) –

+0

Каков аргумент для того, чтобы заставить операторов, не являющихся членами, друзей обоих классов? Это просто удобство, потому что вам не нужно писать методы «getter», или есть какой-то стилистический аргумент? – 2009-03-31 19:55:03

1

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

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 

и вы должны предложить эти три операции (при условии, транспонировать это свойство вектора):

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 

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