Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 05.07.2011, 14:55   #1
XuMuK
Пользователь
 
Регистрация: 08.10.2007
Сообщений: 11
Радость Перегрузка бинарного оператора

Есть класс - реализует некую дин. структуру, похожую на дерево.
Необходимо перегрузить бинарный оператор сложения и присваивания
Т.е. если А, В, С объекты данного класса. Необходимо реализовать С=А+В (в перспективе С=(А+В)-D+E&V ... ну и в таком роде).

Проблема в том, что в ходе исполнения А+В, создается некий объект Х (того же класса, что и А,В,С) в который идет суммирование и ссылка на который потом возвращается.
Далее уже этот объект Х передается в операторную ф-цию присваивания, где фактически создается его копия в объект С.
В итоге, после выполнения С=А+В, получаем "левый" объект Х и утечку памяти.
Для этого временного объекта нужно, явно вызвать деструктор, но вопрос когда и как ?

Может кто сталкивался или знает красивое решение сей проблемы
XuMuK вне форума Ответить с цитированием
Старый 05.07.2011, 15:41   #2
_-Re@l-_
C++, Java
Старожил
 
Аватар для _-Re@l-_
 
Регистрация: 10.04.2010
Сообщений: 2,665
По умолчанию

Явно вызвать деструктор? Не перевелись ещё извращенцы на Руси...
Цитата:
В итоге, после выполнения С=А+В, получаем "левый" объект Х и утечку памяти.
Это с чего бы? Вот вам пример своего оператора сложения...
Код:
inline MyClass& operator+(const MyClass& b)
{
    MyClass temp = b; // Неявный копирующий конструктор, кстати
    // тут складываете поля текущего объекта и temp
    return temp;
}
И после выхода из этой функции объект temp автоматически уничтожается(как правило)!
_-Re@l-_ вне форума Ответить с цитированием
Старый 05.07.2011, 15:58   #3
An1ka
C++,DirectX/OpenGL
Форумчанин
 
Регистрация: 09.01.2011
Сообщений: 422
По умолчанию

Цитата:
Сообщение от XuMuK Посмотреть сообщение
Проблема в том, что в ходе исполнения А+В, создается некий объект Х (того же класса, что и А,В,С) в который идет суммирование и ссылка на который потом возвращается.
Надо возвращать не ссылку, а сам объект в таком случаи.
Код:
class MyClass
{
	int i;
public:
	MyClass (): i(1) { cout << "Конструктор \n";}
	inline MyClass operator + ( const MyClass b)
	{
		MyClass temp = b; // Неявный копирующий конструктор, кстати
		temp.i += this->i;// тут складываете поля текущего объекта и temp
		return temp;
	}
};
int main ( )
{
	MyClass A, B, C;
	C = A + B;
return 0;
}
Вот данный код оптимизируется компилятором так, что за время выполнения программы будет вызвано всего 3 конструктора( а больше нам и не надо). Но в итоге всё правильно посчитается и C.i будет равно 2.

Последний раз редактировалось An1ka; 05.07.2011 в 17:28.
An1ka вне форума Ответить с цитированием
Старый 05.07.2011, 17:33   #4
XuMuK
Пользователь
 
Регистрация: 08.10.2007
Сообщений: 11
По умолчанию

Цитата:
Сообщение от _-Re@l-_ Посмотреть сообщение
Явно вызвать деструктор? Не перевелись ещё извращенцы на Руси...

Это с чего бы? Вот вам пример своего оператора сложения...
Код:
inline MyClass& operator+(const MyClass& b)
{
    MyClass temp = b; // Неявный копирующий конструктор, кстати
    // тут складываете поля текущего объекта и temp
    return temp;
}
И после выхода из этой функции объект temp автоматически уничтожается(как правило)!
В таком случае - два прохода по дереву будет. Т.к. оно большое - хотелось бы за один.

Да и при таком подходе и выражении С=А+В , как мне кажется, объект temp не попадет в operator=, т.к. выйдет из зоны видимости и самовыпилится.
Цитата:
Надо возвращать не ссылку, а сам объект в таком случаи
Очень накладно. Класс со структурой может весить и 100 и больше Мб

Последний раз редактировалось XuMuK; 05.07.2011 в 17:37.
XuMuK вне форума Ответить с цитированием
Старый 05.07.2011, 18:13   #5
Сыроежка
Форумчанин
 
Регистрация: 01.07.2011
Сообщений: 423
По умолчанию

Цитата:
Сообщение от _-Re@l-_ Посмотреть сообщение
Явно вызвать деструктор? Не перевелись ещё извращенцы на Руси...

Это с чего бы? Вот вам пример своего оператора сложения...
Код:
inline MyClass& operator+(const MyClass& b)
{
    MyClass temp = b; // Неявный копирующий конструктор, кстати
    // тут складываете поля текущего объекта и temp
    return temp;
}
И после выхода из этой функции объект temp автоматически уничтожается(как правило)!
У вас совершенно некорректный код, так как вы возвращается ссылку на временный объект, который, как вы правильно заметили, удаляется по выходу из функции.

Ваш пример - это пример того, как не надо перегружать операторы!
Со мной можно встретиться на www.clipper.borda.ru
Сыроежка вне форума Ответить с цитированием
Старый 05.07.2011, 18:20   #6
Сыроежка
Форумчанин
 
Регистрация: 01.07.2011
Сообщений: 423
По умолчанию

Цитата:
Сообщение от An1ka Посмотреть сообщение
Надо возвращать не ссылку, а сам объект в таком случаи.
Код:
class MyClass
{
	int i;
public:
	MyClass (): i(1) { cout << "Конструктор \n";}
	inline MyClass operator + ( const MyClass b)
	{
		MyClass temp = b; // Неявный копирующий конструктор, кстати
		temp.i += this->i;// тут складываете поля текущего объекта и temp
		return temp;
	}
};
int main ( )
{
	MyClass A, B, C;
	C = A + B;
return 0;
}
Вот данный код оптимизируется компилятором так, что за время выполнения программы будет вызвано всего 3 конструктора( а больше нам и не надо). Но в итоге всё правильно посчитается и C.i будет равно 2.
И ваш пример также является примером того, как не следует перегружать операторы. Конечно никакой серьезной ошибки нет, тем не менее в качестве аргумента лучше передавать не временный объект, а констатную ссылку на исходный объект. Иначе накладные расходы по вызовам конструкторов и деструкторов временных объектов сильно возрастают.

Кроме того желательно возвращать констатнтный объект, так как это более соответствует результатм операций со встроенными типами. То есть правильный код оператора суммирования будет выглядеть так:

Код:
inline const MyClass operator + ( const MyClass &b) const
{
	return ( MyClass( i + b.i ) );
}
Правда вам следует в ваш кдасс добавить конструктор с одним параметром!
Со мной можно встретиться на www.clipper.borda.ru

Последний раз редактировалось Сыроежка; 05.07.2011 в 18:30.
Сыроежка вне форума Ответить с цитированием
Старый 05.07.2011, 18:25   #7
Carbon
JAVA BEAN
Участник клуба
 
Аватар для Carbon
 
Регистрация: 22.04.2007
Сообщений: 1,329
По умолчанию

Цитата:
Сообщение от XuMuK Посмотреть сообщение
В таком случае - два прохода по дереву будет. Т.к. оно большое - хотелось бы за один.
Оператор сложения пишется так:
Код:
C operator + (const B & arg) const
{
    C temp = ...; // Временный объект
    ....
    return temp;
}
Оператор присваивания так:
Код:
A & operator = (const A & arg)
{
    if (this != &arg) // или по-другому определять, что объекты равны
    {
        // присваивание
    }
    return *this;
}
Если нужен один проход по дереву и чтоб без создания промежуточных объектов, то нужен итератор и там уже операции +, -, ++, -- и т.д.
Там накладные расходы на создание временных объектов будут минимальны, настолько, что и передавать их в функции можно будет не по ссылке, а копиями.
Carbon вне форума Ответить с цитированием
Старый 05.07.2011, 20:13   #8
Carbon
JAVA BEAN
Участник клуба
 
Аватар для Carbon
 
Регистрация: 22.04.2007
Сообщений: 1,329
По умолчанию

Прекращаем холивар! Ещё одно сообщение из цикла "возврат ссылки/значения" и начну раздавать нарушения!

По теме: Тема была в правильной реализации операторов для дерева. Пусть автор отпишется, иначе тему закрываю.
Carbon вне форума Ответить с цитированием
Старый 05.07.2011, 21:58   #9
An1ka
C++,DirectX/OpenGL
Форумчанин
 
Регистрация: 09.01.2011
Сообщений: 422
По умолчанию

Цитата:
Сообщение от Сыроежка Посмотреть сообщение
И ваш пример также является примером того, как не следует перегружать операторы. Конечно никакой серьезной ошибки нет, тем не менее в качестве аргумента лучше передавать не временный объект, а констатную ссылку на исходный объект. Иначе накладные расходы по вызовам конструкторов и деструкторов временных объектов сильно возрастают.
Только где эти накладные расходы ? Вы хоть код то запускали и проверяли ? В реальности нет никаких накладых раходов... Компиляторы умеют всё оптимизировать То, что написано в старых книжках очень сильно отличается от реальных "военно-полевых" условий !

Цитата:
Сообщение от Сыроежка Посмотреть сообщение
Кроме того желательно возвращать констатнтный объект
Константный не обязательно возвращать ! При возврате константного объекта некоторые операции будут не доступны, например :
(A+B).func () - которая изменяет объект.
An1ka вне форума Ответить с цитированием
Старый 06.07.2011, 16:25   #10
Сыроежка
Форумчанин
 
Регистрация: 01.07.2011
Сообщений: 423
По умолчанию

Цитата:
Сообщение от An1ka Посмотреть сообщение
Только где эти накладные расходы ? Вы хоть код то запускали и проверяли ? В реальности нет никаких накладых раходов... Компиляторы умеют всё оптимизировать То, что написано в старых книжках очень сильно отличается от реальных "военно-полевых" условий !


Константный не обязательно возвращать ! При возврате константного объекта некоторые операции будут не доступны, например :
(A+B).func () - которая изменяет объект.
Объясняю по порядку. Ког8да вы передаете в качестве параметра объект по значению, то 1) вызывается конструктор для этого объекта; 2) при завершении оператора-функции вызывается деструктор этого объекта. У вас очевидно малый опыт программирования, но порой объекты бывают достаточно сложные. Например, конструкторы могут выделять память для своих членов, открытвать файлы и т.д. К тому же сами члены объекта также могут требовать вызова конструкторов и деструкторов. Кроме того в самом стеке функции должна быть выделена память под весь объект. Какой смысл передавать объект по значению, если вы с ним ничего не делаете?! Не проще ли передать вместо всего объекта через стек лишь константную ссылку на него? Сравните размер ссылки и размер объекта!

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

Код:
int x = 3, y = 4;

x + y = 5;
Такое повдение ожидают пользователи вашего кода и от вами определнных типов, то есть семантика операций должна быть идентична для встроенных типов и ваших типов. Этот принцип называется принципом наибольшей предсказуемости поведения.
Со мной можно встретиться на www.clipper.borda.ru

Последний раз редактировалось Сыроежка; 06.07.2011 в 17:06.
Сыроежка вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
C++ Перегрузка оператора += и >> Jane-sad Помощь студентам 4 01.03.2013 15:38
Перегрузка бинарного оператора + EC.cpp Общие вопросы C/C++ 4 10.04.2011 00:32
Перегрузка оператора + Jane-sad Помощь студентам 0 05.10.2010 13:52
перегрузка оператора -> alex_alpha Общие вопросы C/C++ 5 23.06.2010 19:07
Перегрузка оператора Crucian Общие вопросы C/C++ 2 22.10.2007 09:44