Skip to content

CPP 10. Перегрузка операторов в CPP. Операторы .*, *. Правила перегрузки операторов. Перегрузка операторов =, () и [ ]. Перегрузка операторов `стрелочка`, * и `стрелочка`*.

Dmitriy Pisarenko edited this page Jun 15, 2023 · 2 revisions

Перегрузка операторов в С++

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

Идея

  • Мы создаем свое данное. Почему бы нам для этого данного не определить какие-либо операции? Ведь для стандартных данных они есть.
  • Мы должны задать знак операции, сказать, какая это операция (указать арность). Она может быть унарной, бинарной, тернарной. Операция может выполняться либо слева направо, либо справа налево. Операции имеют приоритет.
  • Мы не можем задавать новые операторы, а на основе существующих операторов создавать новые операции, то есть перегружать оператор.

6 операторов, которые перегружать нельзя:

  1. . - доступ к члену объекта. Если мы перегрузим этот оператор, мы не сможем вызвать для объекта ни одного метода!
  2. .* - указатель на метод.
  3. :: - оператор доступа к контексту. Он применяется для доступа к членам через имя класса или для доступа к глобальному контексту.
  4. ? : - тернарный оператор. Разработчики просто не смогли придумать, как перегрузить этот оператор. Страуструп не пришел ни к одному из решений.
  5. sizeof - определение размера объекта. Если мы перегрузим, мы такое вытворим в программе!
  6. typeid - возвращает id типа объекта. Если мы перегрузим, мы не сможем идентифицировать объект и понять, какого он типа.

Указатель на метод:

    auto pf = &A::f();
    A obj;
    (obj.*pf)(); // .* имеет ниже приоритет чем ()

->* аналогичен .*, только работаем с указателем на объект.

Правила перегрузки операторов

  1. =, (), [], ->, ->* перегружаются только как члены класса
  2. Бинарные операторы можно перегружать или как члены класса (более предпочтительно), или как внешние функции
  3. Унарные перегружаем как члены класса.
  4. &&, ||, , , & особенность операторов в том, что второй операнд может не вычисляться, но при перегрузке он будет вычислен обязательно

Перегрузка оператора =: копирование, перенос

    Array& Array::operator=(const Array& arr)
    {
      if( this == &arr ) return *this;
    	if ( this->count != arr.count) 
    	{
    	  delete []this->mas;
    	  this->count = arr.count;
    	  this->mass = new double[this->count];
    	}
    	memcpy(this->mass, arr.mass, this->count * sizeof(double)); // плохая функция, но лень цикл писать
    	return *this;
    }
     
    Array& Array::operator=(Array&& arr)
    {
      if( this == &arr ) return *this;
    	if ( this->count != arr.count) 
    	{
    	  delete []this->mas;
    	  this->count = arr.count;
    	}
    	this->mass = arr.mas;
    	arr.mas = nullptr;
    	return *this;
    }

Перегрузка оператора ()

#include <iostream>

class MyFunctor {
public:
    void operator()(int x, int y) const {
        std::cout << "Sum = " << x + y << std::endl;
    }
};

int main() {
    MyFunctor myFunctor;
    myFunctor(3, 4);
    return 0;
}

Перегрузка оператора []

    double& Array::operator[](const Index& index)
    {
      if (index < 0 || index >= cnt) throw std::out_of_range("Error: class Array operator [];");
      return mas[index];
    }

Перегрузка оператора ->

Оператор -> перегружается как член класса, он унарный, принимающий один параметр - в данном случае this будет принимать, и должен возвращать либо указатель, либо ссылку на объект.

class A
{
public:
	void f();
};

class B
{
public:
    A* operator->();
};

// Создадим объект где-то в коде и вызовем оператор ->
// Такая запись будет означать: оператор возвращает указатель на объект класса A
// и для него, для объекта, мы вызываем метод f()
B obj;
obj->f(); // (obj.operator->())->f()

Перегрузка оператора *

#include <iostream>

class MyPointer {
private:
    int* ptr;
public:
    MyPointer(int* p) : ptr(p) {}
    int& operator*() const {
        return *ptr;
    }
};

int main() {
    int x = 42;
    MyPointer ptr(&x);
    std::cout << "x = " << *ptr << std::endl;
    *ptr = 17;
    std::cout << "x = " << *ptr << std::endl;
    return 0;
}

Перегрузка оператора ->*

Оператор ->* перегружается как член класса и является бинарным (*this и указатель на метод)

class Callee
{
private:
	int index;
public:
	Callee(int i = 0) : index(i) {}
	int inc(int d) { return index += d; }
};

class Caller
{
public:
	typedef int (Callee::*FnPtr)(int); // указан тип
private:
	Callee* pobj;
	FnPtr ptr;
public:
	Caller(Callee* p, FnPtr pf) : pobj(p), ptr(pf) {}
	int operator ()(int d) { return (pobj->*ptr)(d); } // functor
};

class Pointer
{
private:
	Callee* pce;
public:
	Pointer(int i) { pce = new Callee(i); }
	~Pointer() { delete pce; }
	Caller operator->*(Caller::FnPtr pf) { return Caller(pce, pf); } // принимающий указатель на метод
};

void main()
{
	Caller::FnPtr pn = &Callee::inc;
	Pointer pt(1);
	cout<<"Result: "<<(pt->*pn)(2)<<endl; // (pt.operator->*(pn)).operator()(2)
}

Этот класс Pointer по существу скрывает связь объекта который выбирает связку и указателя на метод. Создаем объект и вызывая перегруженный оператор, он возвращает нам объект, который отвечает за связь и этот объект имеет перегруженный оператор круглые скобочки. Он уже вызывает указатель.

Clone this wiki locally