Возвращать из бинарного оператора ссылку 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 просмотров]