БлогNot. Возвращать из бинарного оператора ссылку Class & или объект класса Class?

Возвращать из бинарного оператора ссылку Class & или объект класса Class?

Речь идёт о переопределённых в классах C++ функциях-операторах.

Увы, однозначного ответа на вопрос из заголовка нет, а холивары можно вести долго.

Случается, что возвращать из функции-оператора только ссылку на объект, а не "весь" объект, экономичнее, случается также, что нужно поддерживать для ряда операторов вычисления по цепочке и учитывать нюансы, связанные с конструктором копирования и оператором "=".

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

Реализуем простейшего кота:

#include <iostream>
using namespace std;

class Cat {
 int value;
public:
 int val() const { return value; }
 Cat (int value = 1) { this->value = value; }
 //Здесь будет переопределённый оператор сложения котов
};

ostream& operator << (ostream& out, const Cat& f) { //Вывод кота в ostream
 return out << f.val() << endl;
}

int main() {
 //Здесь будем вызывать наш оператор
 //cin.get(); //До версии 2019
 return 0;
}

Переопределим в этом классе бинарный оператор для сложения котов. Даже если не брать заведомо сомнительные решения вроде нарушающих инкапсуляцию функций-"друзей" или функций с неверным количеством аргументов ( Cat operator+(Cat a, Cat b) ), всё равно возможны несколько решений.

Вариант 1 такой:

Cat * operator + (Cat b) {
 Cat * cat = new Cat(this->value + b.value);
 return cat;
}

//в функции main:
Cat a(1),b(2),c = *(a + b);
cout <<  c;

Чем плох этот код? Здесь мы возвращаем не кота, а указатель на кота, созданного в "куче".

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

Правда, при возвращении значения из функции мы передаем всего sizeof(Cat *) байт, а не sizeof(Cat) (представим, что у кота много свойств).

Сложение котов по цепочке также будет выглядеть страшновато, нам самим придётся управлять порядком счёта:

//в функции main:
Cat a(1),b(2),c(3),
d = *(a + *(b + c));
cout <<  d;

Вариант 2:

Cat & operator + (Cat b) {
 Cat *temp = new Cat(val()+b.val());
 return *temp;
}

//в функции main:
Cat a(1),b(2), c = a + b;
cout <<  c;

Здесь вместо указателя возвращается ссылка на объект, созданный в "куче". Но у нас остаётся та же проблема сохранения значения в вызывающей функции и освобождения памяти, если таковое понадобится выполнить в явном виде.

По цепочке котов теперь можно складывать в естественном виде:

//в функции main:
Cat a(1),b(2),c(3),
d = a + b + c;
cout <<  d;

Вариант 3, возвращаем готового кота:

Cat operator + (Cat b) {
 return Cat(this->value + b.value);
}

Код в main - такой же, как в предыдущем варианте. В этом и следующем варианте кода также не будет никаких проблем с работой оператора по цепочке вычислений.

Здесь мы возвращаем через стек целого кота, а не адрес в памяти ранее созданного кота.

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

Вариант 4, если кот большой (в смысле не значения value, а объёма данных класса), есть смысл передавать его в оператор не по значению, а по ссылке:

Cat operator + (const Cat& b) {
 return Cat(this->value + b.value);
}

Код в main такой же, как в вариантах 2-3, наверное, этот кот самый предпочтительный.

Фрагменты проверялись в консоли Visual Studio 2019.

13.04.2020, 15:08 [1079 просмотров]


теги: учебное программирование c++

показать комментарии (1)