информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
За кого нас держат?Атака на Internet
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Бэкдор в xz/liblzma, предназначенный... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
если вы видите этот текст, отключите в настройках форума использование JavaScript
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
[C++] Согласен 11.04.03 16:34  Число просмотров: 1050
Автор: Ktirf <Æ Rusakov> Статус: Elderman
<"чистая" ссылка>
> На самом деле я бы делал примерно как у страуструпа: с
> разделяемым представлением и отложенным копированием. Если
> бы вообще стал этим заниматься.
Для char * за нас уже постарались :)

> Мне и std::string нравится
Exactly.
<programming>
[C++] Перегрузка operator+ 10.04.03 06:23  
Автор: makeworld Статус: Member
Отредактировано 10.04.03 07:10  Количество правок: 1
<"чистая" ссылка>
Есть примерно такой класс:

class TESTCLASS {
private:
char *ptr;
//.......
public:
TESTCLASS(char *);
~TESTCLASS();
void operator=(char *);
TESTCLASS operator+(char *);
//.......
};

TESTCLASS::TESTCLASS(char *str) {
int i=length(str); // в i длинна str (без завершающего нулевого байта)
ptr = new char [i+1];
copy(ptr,str); // копирование ptr в str и завершение str '\0'
}

TESTCLASS::~TESTCLASS() {
delete [] ptr;
}

Есть в этом классе перегруженный operator+, осуществляющий сложение двух строк (TESTCLASS и char*) и возвращающий результат типа TESTCLASS, что-то вроде этого:

TESTCLASS TESTCLASS::operator+(char *s) {
char *result = new char [length(ptr)+length(s)+1];
// копирование в result ptr и s;
return TESTCLASS(result); //*
}

Подобный код вызывает ошибки во время исполнения (Debug Assertion Failed при Debug-компиляции), что в принципе понятно, т.к. после завершения работы перегруженного оператора должен вызваться деструктор и освободить память выделенную в * и при таком коде t1.ptr будет указывать на уже освобожденную облать памяти:
TESTCLASS t1("AAAAAAAAAAA");
t1 = t1 + " zlooo";

Компилятор у меня VC++ .NET

Подскажите как корректно перегрузить operator+, чтобы он мог складывать две строки и возвращать значение типа TESTCLASS. И вообще, как лучше создавать объект класса (содержащий указатели) внутри метода и возвращать его.
[C++] Ошибочка 10.04.03 18:16  
Автор: Ktirf <Æ Rusakov> Статус: Elderman
Отредактировано 10.04.03 18:18  Количество правок: 1
<"чистая" ссылка>
Ключевым моментом является возвращение значения, как ты правильно понимаешь. return TESTCLASS(result) приводит к следующим действиям:

1. Создается новый объект TESTCLASS. В него копируется через new[] строка, переданная параметром (только что скопированная, замечу, и тоже через new[]).
2. Создается копия этого объекта при помощи конструктора копирования по умолчанию (поскольку сам ты его не определял). При этом на память, на которую ссылается указатель внутри старого объекта, ссылается и указатель внутри нового объекта (!)
3. Удаляется старый объект. В деструкторе вызывается delete[], в результате память, на которую ссылаются указатели И старого, И нового объекта, теперь заполнена мусором.
4. При выходе из функции теряется указатель result! (этого ты не заметил). Память, на которую он смотрел, больше недостижима, сиречь имеет место утечка памяти.
5. Копия объекта (т. е. новый объект) передается вызвавшей функции. К сожалению, указатель внутри него ссылается на мусор.

Итого имеем две ошибки. Теперь как это сделать правильно. Не знаю, почему ты не воспользовался std::string для хранения строки внутри TESTCLASS. Если особых причин нет, лучше используй его, тогда про управление памятью ты сможешь забыть. Если же особые причины есть, я могу тебе помочь написать все то же правильно, хотя верю, что ты и сам справишься теперь, когда знаешь ошибки.

Насчет конструктора копирования соображение тут было совершенно правильное. Лечением от указателя на мусор может быть так называемое "глубокое копирование", когда копируется не указатель, а память, на которую он указывает. Другими словами, вместо
ptr = old_object.ptr
должно быть
ptr = new char[length(old_object.ptr)]; copy(ptr, old_object.ptr);
Надеюсь, идея понятна.

Но с result проблема все равно остается, хотя она-то как раз решается совсем просто.

Напоследок: настоятельно рекомендую прочитать хотя бы Скотта Мейерса, "Эффективное использование C++", дабы боьше не делать подобных глупостей.
Нет конструктора копии 10.04.03 06:40  
Автор: SSf Статус: Незарегистрированный пользователь
<"чистая" ссылка>
У тебя не определен конструктор копии:
TESTCLASS(const TESTCLASS& v){...}
Поэтому при возврате из оператора + происходит побайтное копирование и деструктор временного обьекта удаляет ptr, ну а остальное очевидно.
Как написать правильно подсказать не могу потому что если использовать перегруженные операторы необходимо создавать временные классы что приводит к потере производительности, может лудьше использывать методы?
Нет конструктора копии 10.04.03 07:21  
Автор: makeworld Статус: Member
<"чистая" ссылка>
> У тебя не определен конструктор копии:
> TESTCLASS(const TESTCLASS& v){...}

а зачем мне конструктор копии, что-то я не очень понял..?

> Поэтому при возврате из оператора + происходит побайтное
> копирование и деструктор временного обьекта удаляет ptr, ну
> а остальное очевидно.
> Как написать правильно подсказать не могу потому что если
> использовать перегруженные операторы необходимо создавать
> временные классы что приводит к потере производительности,
> может лудьше использывать методы?

Я бы рад сделать просто методы, но от меня это не зависит.
Зачем конструктор копии 10.04.03 10:57  
Автор: SSf Статус: Незарегистрированный пользователь
<"чистая" ссылка>
При возврате из operator + ты возвращаеш обьект но этот обьект создан в стеке функции чтобы передать его в другую функцию в ее стеке создается обьект этого же класса и вызывается конструктор копии, так у тебя будет происходить при вызове оператора = (надеюсь у тебя он определен).
А ввобще проставь printf в конструкторы и деструкторы и увидиш сам когда что вызывается
И еще 10.04.03 19:02  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> При возврате из operator + ты возвращаеш обьект но этот
> обьект создан в стеке функции чтобы передать его в другую
> функцию в ее стеке создается обьект этого же класса и
> вызывается конструктор копии, так у тебя будет происходить
> при вызове оператора = (надеюсь у тебя он определен).
> А ввобще проставь printf в конструкторы и деструкторы и
> увидиш сам когда что вызывается
Все тут правильно говорили. Но еще я бы советовал возвращать не по значению, а по ссылке хотя бы. Тогда и копироваться будет меньше. Но нужно учесть, если будешь возвращать по ссылке - конструируй объект не на стеке, а или в куче или в сегменте данных. Короче примерно так

TESTCLASS &TESTCLASS::operator+(char *s) { char *buf = new char [length(ptr)+length(s)+1]; // копирование в result ptr и s; TESTCLASS *p = new TESTCLASS(buf); //*delete[] buf; return *p; }

Там еще много чего оптимизировать можно. Но я так понял, оптимальность - не главное. Абы работало
[C++] Спорно 10.04.03 20:18  
Автор: Ktirf <Æ Rusakov> Статус: Elderman
Отредактировано 10.04.03 20:20  Количество правок: 1
<"чистая" ссылка>
> Все тут правильно говорили. Но еще я бы советовал
> возвращать не по значению, а по ссылке хотя бы. Тогда и
> копироваться будет меньше. Но нужно учесть, если будешь
> возвращать по ссылке - конструируй объект не на стеке, а
> или в куче или в сегменте данных. Короче примерно так
>
> > TESTCLASS &TESTCLASS::operator+(char *s) { > char *buf = new char [length(ptr)+length(s)+1]; > // копирование в result ptr и s; > TESTCLASS *p = new TESTCLASS(buf); //*> delete[] buf; > return *p; > } >
А удалять это кто будет?
TESTCLASS a("a"), b("b"), c("c"); TESTCLASS d = a + b + c;
И как удалять временный объект, возникший от a+b??? Это, кстати, тема одного из советов у Мейерса. Нельзя использовать ни ссылки, ни указатели - ничего. К сожалению. Здесь должно быть копирование. Всю оптимизацию, буде таковая возможна, проделают компиляторы.
[C++] Согласен 11.04.03 16:15  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
На самом деле я бы делал примерно как у страуструпа: с разделяемым представлением и отложенным копированием. Если бы вообще стал этим заниматься. Мне и std::string нравится и вообще я CPP-ой не часто пользуюсь, в основном plain C. А это я на скорую руку пытался подтянуть, чтоб хоть как то работало. И тут действительно будут leak-и.

2 Messer: Советую дважды задуматься перед тем как использовать то что я написал.
[C++] Согласен 11.04.03 16:34  
Автор: Ktirf <Æ Rusakov> Статус: Elderman
<"чистая" ссылка>
> На самом деле я бы делал примерно как у страуструпа: с
> разделяемым представлением и отложенным копированием. Если
> бы вообще стал этим заниматься.
Для char * за нас уже постарались :)

> Мне и std::string нравится
Exactly.
всем спасибо, разобрался 11.04.03 06:34  
Автор: makeworld Статус: Member
<"чистая" ссылка>
сделаю так, как показал amirul.
А с утечками памяти как разбираться будешь? 11.04.03 14:31  
Автор: Ktirf <Æ Rusakov> Статус: Elderman
<"чистая" ссылка>
А с утечками памяти как разбираться будешь? 12.04.03 06:51  
Автор: makeworld Статус: Member
Отредактировано 12.04.03 06:55  Количество правок: 1
<"чистая" ссылка>
точнее сделал не совсем так, как amirul..

добавил конструктор:
DSTR::DSTR(const DSTR &ds) {
	int len = ln(ds.dstr);
	if (dstr = new char[len+1])
		copy(dstr,ds.dstr,len);
	else
		exit(1);
}

---

и переделал operator+:
DSTR DSTR::operator+(char *st) {
	int len = ln(st);
	DSTR retval;
	char *tmp;
	if (!len) return this->dstr;
	if (dstr == NULL) {
		tmp = new char[len+1];
		copy(dstr,st,len);
		retval = DSTR(tmp);
		delete [] tmp;
		return retval;
	}
	int len1 = ln(dstr);
	tmp = new char [len+len1+1];
	copy(tmp,dstr,len1);
	copy(tmp+len1,st,len);
	retval = DSTR(tmp);
	delete [] tmp;
	return retval;
}

---

явных утечек памяти не замечается, если что-нибудь обнаружится буду править по мере сил.
[C++] Придраться вроде не к чему кроме одного 13.04.03 01:17  
Автор: Ktirf <Æ Rusakov> Статус: Elderman
<"чистая" ссылка>
Если уж ты в конструкторе написал проверку успешного выделения памяти,
> 	if (dstr = new char[len+1])
> 		copy(dstr,ds.dstr,len);
> 	else
> 		exit(1);

---
то чем провинился operator+? :)
> 	if (dstr == NULL) {
> 		tmp = new char[len+1];
> 		copy(dstr,st,len);
...
> 	int len1 = ln(dstr);
> 	tmp = new char [len+len1+1];
> 	copy(tmp,dstr,len1);

---
[C++] Придраться вроде не к чему кроме одного 13.04.03 04:58  
Автор: makeworld Статус: Member
Отредактировано 13.04.03 05:04  Количество правок: 1
<"чистая" ссылка>
> Если уж ты в конструкторе написал проверку успешного
> выделения памяти, то чем провинился operator+? :)

Сейчас у меня проверка успешного выделения памяти везде, где выделяется память под саму строку (char *dstr). Если не выделилась - exit(1). А для вспомогательный переменных сделаю что-нить типа флаговой переменной memflag, которая по дефолту=0 и увеличивается при каждой ошибке выделения памяти. В конце каждого метода либо проверка if(memflag) return -2; либо еще что-нибудь..
[C++] Раз уж пользуешься CPP, используй exception-ы 13.04.03 16:00  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
Будет намного нагляднее. Тем более, что как написано в MSDN:
The default behavior of a new handler is to throw an object of type bad_alloc. A null pointer designates the default new handler.

> > Если уж ты в конструкторе написал проверку успешного
> > выделения памяти, то чем провинился operator+? :)
>
> Сейчас у меня проверка успешного выделения памяти везде,
> где выделяется память под саму строку (char *dstr). Если не
> выделилась - exit(1). А для вспомогательный переменных
> сделаю что-нить типа флаговой переменной memflag, которая
> по дефолту=0 и увеличивается при каждой ошибке выделения
> памяти. В конце каждого метода либо проверка if(memflag)
> return -2; либо еще что-нибудь..
Если у тебя выполнилась следующая после new инструкция, значит память выделилась нормально. Если ты не будешь ловить bad_alloc, то когда стек свернется за пределы main-а, там стоит дефолтовый обработчик эксепшнов, который вызывает terminate, которая по дефолту вызывает abort. Короче, не надо ничего писать и тогда ты вывалишься из проги если память выделилась неудачно.

Если тебе это не подходит, пользуйся или new(nothrow) (нужно сделать #include <new> для использования) или сделать:

try { // Любое количество new без проверки успешности } catch(bad_alloc ba) { // Освободить немного памяти и попытаться еще раз }

Если компилятор поддерживает стандарт C++ от 98-го года (номер не помню, но принимался он ISO/IEC), то все должно работать правильно. VC начиная с 6-го пытаются поддерживать этот стандарт (хотя я еще не слышал ни об одном компилере, который бы полностью соответствовал этому стандарту).
1




Rambler's Top100
Рейтинг@Mail.ru


  Copyright © 2001-2024 Dmitry Leonov   Page build time: 0 s   Design: Vadim Derkach