Принятый ответ Майкла Берра довольно хорош в объяснении техники, но из комментариев кажется, что помимо «как» вас интересует «почему». Основными причинами перегрузки операторов для данного типа являются улучшение читабельности и обеспечение необходимого интерфейса.
Если у вас есть тип, для которого существует единственное общепринятое значение для оператора в области вашей проблемы, то обеспечение этого при перегрузке оператора делает код более читабельным:
std::complex<double> a(1,2), b(3,4), c( 5, 6 );
std::complex<double> d = a + b + c; // compare to d = a.add(b).add(c);
std::complex<double> e = (a + d) + (b + c); // e = a.add(d).add( b.add(c) );
Если у вашего типа есть заданное свойство, которое естественно будет выражаться оператором, вы можете перегрузить этот конкретный оператор для вашего типа. Рассмотрим, например, что вы хотите сравнить ваши объекты на равенство. Предоставление operator==
(и operator!=
) может дать вам простой читаемый способ сделать это. Это имеет преимущество в выполнении общего интерфейса, который может использоваться с алгоритмами, которые зависят от равенства:
struct type {
type( int x ) : value(x) {}
int value;
};
bool operator==( type const & lhs, type const & rhs )
{ return lhs.value == rhs.value; }
bool operator!=( type const & lhs, type const & rhs )
{ return !lhs == rhs; }
std::vector<type> getObjects(); // creates and fills a vector
int main() {
std::vector<type> objects = getObjects();
type t( 5 );
std::find( objects.begin(), objects.end(), t );
}
Обратите внимание, что при реализации алгоритма find
он зависит от определяемого ==
. Реализация find будет работать с примитивными типами, а также с любым пользовательским типом, для которого определен оператор равенства. Существует общий единый интерфейс, который имеет смысл . Сравните это с версией Java, где сравнение типов объектов должно выполняться с помощью функции-члена .equals
, тогда как сравнение типов примитивов может быть выполнено с помощью ==
. Позволяя вам перегружать операторы, вы можете работать с пользовательскими типами так же, как с примитивными типами.
То же самое касается заказа. Если в домене вашего класса есть четко определенный (частичный) порядок, то предоставление operator<
является простым способом реализации этого порядка. Код будет читабелен, а ваш тип будет использоваться во всех ситуациях, когда требуется частичный порядок, например, внутри ассоциативных контейнеров:
bool operator<( type const & lhs, type const & rhs )
{
return lhs < rhs;
}
std::map<type, int> m; // m will use the natural `operator<` order
Типичной ошибкой при введении в язык перегрузки операторов является ' золотой молот '. Если у вас есть золотой молот, все выглядит как гвоздь, а перегрузка оператора злоупотребляется.
Важно отметить, что причина перегрузки в первую очередь заключается в улучшении читабельности. Читаемость улучшается только в том случае, если, когда программист смотрит на код, цели каждой операции на первый взгляд понятны без необходимости читать определения. Когда вы видите, что добавляются два комплексных числа, например a + b
, вы знаете, что делает код. Если определение оператора не является естественным (вы решаете реализовать его как добавление только реальной его части), тогда код станет сложнее для чтения, чем если бы вы предоставили (член) функцию. Если значение операции не определено для вашего типа, происходит то же самое:
MyVector a, b;
MyVector c = a + b;
Что такое c
? Является ли это вектором, где каждый элемент i
является суммой соответствующих элементов из a
и b
, или это вектор, созданный путем объединения элементов a
перед элементами b
. Чтобы понять код, вам нужно перейти к определению операции, а это значит, что перегрузка оператора менее читаема, чем предоставление функции:
MyVector c = append( a, b );
Набор операторов, которые могут быть перегружены, не ограничивается арифметическими и реляционными операторами. Вы можете перегрузить operator[]
, чтобы индексировать тип, или operator()
, чтобы создать вызываемый объект, который можно использовать как функцию (это называется функторами) или который упростит использование класса:
class vector {
public:
int operator[]( int );
};
vector v;
std::cout << v[0] << std::endl;
class matrix {
public:
int operator()( int row, int column );
// operator[] cannot be overloaded with more than 1 argument
};
matrix m;
std::cout << m( 3,4 ) << std::endl;
Существуют и другие варианты перегрузки операторов. В частности, operator,
может быть перегружен действительно причудливыми способами для целей метапрограммирования, но это, вероятно, намного сложнее, чем то, что вам действительно нужно сейчас.