> На самом деле я бы делал примерно как у страуструпа: с > разделяемым представлением и отложенным копированием. Если > бы вообще стал этим заниматься. Для 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. И вообще, как лучше создавать объект класса (содержащий указатели) внутри метода и возвращать его.
Ключевым моментом является возвращение значения, как ты правильно понимаешь. 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, ну > а остальное очевидно. > Как написать правильно подсказать не могу потому что если > использовать перегруженные операторы необходимо создавать > временные классы что приводит к потере производительности, > может лудьше использывать методы?
Я бы рад сделать просто методы, но от меня это не зависит.
При возврате из 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;
}
Там еще много чего оптимизировать можно. Но я так понял, оптимальность - не главное. Абы работало
> Все тут правильно говорили. Но еще я бы советовал > возвращать не по значению, а по ссылке хотя бы. Тогда и > копироваться будет меньше. Но нужно учесть, если будешь > возвращать по ссылке - конструируй объект не на стеке, а > или в куче или в сегменте данных. Короче примерно так > > > 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: Советую дважды задуматься перед тем как использовать то что я написал.
> На самом деле я бы делал примерно как у страуструпа: с > разделяемым представлением и отложенным копированием. Если > бы вообще стал этим заниматься. Для char * за нас уже постарались :)
> Мне и std::string нравится Exactly.
всем спасибо, разобрался11.04.03 06:34 Автор: makeworld Статус: Member
> Если уж ты в конструкторе написал проверку успешного > выделения памяти, то чем провинился 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-го пытаются поддерживать этот стандарт (хотя я еще не слышал ни об одном компилере, который бы полностью соответствовал этому стандарту).