Помогите мне плZ, а то совсем мозги уже потекли.
Имеем простой до безобразия строковой класс XString.
Почему при создании объекта этого класса способом
XString s = "string";
вызывается второй конструктор XString(LPSTR string),
а не просто XString, с последующим вызовом перегруженного
оператора = ?
И самое главное, почему при присваивании одного
объекта этого класса другому вообще вылетает Debug
Assertion Fault, хотя присваивается то все нормально.
Насколько я понимаю, я что-то очень нехорошее замутил
с new/delete. Далее приведен исходник.
Visual C++ 5.0 — DEBUG
//-----------------------------------------------------------------------------------#include <windows.h>
class XString
{
public:
char *Text;
WORD Size;
XString();
XString(LPSTR string);
~XString();
void SetText(LPSTR string);
void operator =(LPSTR string);
};
XString::XString()
{
Text = NULL;
Size = 0;
}
XString::XString(LPSTR string)
{
Text = NULL;
Size = 0;
SetText(string);
}
XString::~XString()
{
if(Text)
{
delete Text;
Text = NULL;
}
}
void XString::SetText(LPSTR string)
{
int i = 0;
while(string[i]) i ++;
Size = i + 1;
if(Text) delete Text;
Text = new char[Size]; //Вот эта строчка вызывает fault при присваиванииfor(i = 0; i < Size; i ++)
Text[i] = string[i];
}
void XString::operator =(LPSTR string)
{
SetText(string);
}
void main()
{
XString str1 = "Just a text string\r\n";
XString str2 = "Another text string\r\n";
str1 = str2; //А это само присваивание ;))
}
Потому что initialization и assignment — это две разные вещи в С++
Читай раздел Explicit Initialization, например, в том же самом MSDN.
Note that because the equal sign (=) in the context of initialization is different from an assignment operator, overloading operator= has no effect on initialization.
1) new[] --> delete[]
2) SetText не проверяет, создан ли ранее буфер (должен либо повторно использовать, либо уничтожить)
3) вместо while(string[i]) -- не проще ли использовать strlen(), strcpy() ?
Здравствуйте, ice71crew, Вы писали:
I> Помогите мне плZ, а то совсем мозги уже потекли. I>Имеем простой до безобразия строковой класс XString. I>Почему при создании объекта этого класса способом I>XString s = "string"; I>вызывается второй конструктор XString(LPSTR string), I>а не просто XString, с последующим вызовом перегруженного I>оператора = ? I>И самое главное, почему при присваивании одного I>объекта этого класса другому вообще вылетает Debug I>Assertion Fault, хотя присваивается то все нормально. I>Насколько я понимаю, я что-то очень нехорошее замутил I>с new/delete. Далее приведен исходник.
Запомни простое правило , если ты используешь внутри класса указатель на динамически выделенную память, то
конструктор копирования и оператор присваивания должны быть.
Здравствуйте, ice71crew, Вы писали:
I> Помогите мне плZ, а то совсем мозги уже потекли. I>Имеем простой до безобразия строковой класс XString. I>Почему при создании объекта этого класса способом I>XString s = "string"; I>вызывается второй конструктор XString(LPSTR string), I>а не просто XString, с последующим вызовом перегруженного I>оператора = ?
Потому что operator = здесь совершенно не при чем. Подобная запись означает "инициализацию копированием", и производится с помощью конструктора копии (возможно после вызова конструктора — преобразователя типа).
Давай смотреть, что происходит на самом деле.
XString str1 = "Just a text string\r\n";
Это, как я уже сказал, инициализация копированием. В данном случае возможны 2 варианта:
Вариант1.
Первым делом создается временный объект XString с помощью констркутора XString::XString(LPSTR). Этот временный объект используется в качестве аргумента "настоящего" конструктора копирования, который ты кстати не определил, и поэтому за тебя это сделал компилятор. Однако компилятор не провидец, и не знает, что именно должно происходить при копировании, а посему идет самым простым путем — т.е. производит поэлементное копирование. А значит наш временный объект, и объект str1 содержат указатель на один и тот же блок. После окончания инициализации временный объект разрушается (вызывая в деструкторе delete [] Text), и элемент Text объекта str1 после всего этого указывает в туман. Естественно, любая попытка обратиться по этому адресу (в том числе и delete) ведут к ошибке (и это в лучшем случае).
Вариант 2.
Компилятор обходится без создания временного объекта, и производит инициализацию "напрямую" с использованием XString::XString(LPSTR). В этом случае программа продолжает работу. Однако никто не гарантирует, что она также будет работать при смене компилятора (или версии компилятора ).
XString str2 = "Another text string\r\n";
то же самое.
str1 = str2;
Это присваивание. И естественно, тут имеет место быть вызов operator=. Тонкость только в том, что вызывается
XString::operator = (const XString&)
а совсем не то недоразумение, которое объявлено у тебя. Как видно из кода, "правильного" operator= тоже нет, и поэтому его "сделал" компилятор. И что самое обидное — так же, как и конструктор копии, т.е. ограничился поэлементным копированием. А значит, после записи str1 = str2; оба объекта опять ссылаются на один и тот же блок памяти, п при разрушении этих объектов delete вызовется 2 раза для одного и того же адреса. Последствия, опять же, могут быть самыми необыкновенными.
PS
И еще. Если память выделяется с помощью new [], то и освобождать ее надо с помощью delete []
Пример
Большое спасибо всем ответившим. Standart string вещь безусловно хорошая, просто я не люблю когда что-то делают за меня + надо же мне самому уметь делать такие вещи
Здравствуйте, ice71crew, Вы писали:
I>Большое спасибо всем ответившим. Standart string вещь безусловно хорошая, просто я не люблю когда что-то делают за меня
И что теперь — ты будешь переписывать все подряд?!
I>+ надо же мне самому уметь делать такие вещи
Вот это да. Это аргумент. А то если начать переписывать стандартные вещи, то на работу времени не останется
B>>for(int i = 0; i < sizeof(ch2)/sizeof(ch2[0]); ++i)
B>> delete [] ch2[i];
B>>
H>Неа VC6 ругается ... H>
И правильно делает. Я ошибся. Ты потерял свои указатели на выделенные блоки после строки (1), как совершенно справедливо заметил наш анонимный коллега чуть ниже.
Возможно, ты хотел что-то типа этого?
char *ch2[3];
ch2[0] = new char[8];
ch2[1] = new char[8];
ch2[2] = new char[8];
strcpy(ch2[0], "0000000");
strcpy(ch2[1], "1111111");
strcpy(ch2[2], "2222222");
cout << ch2[0] << ch2[1] << ch2[2] <<endl;
for(int i = 0; i < sizeof(ch2)/sizeof(ch2[0]); ++i)
delete [] ch2[i];
H>>вот как в этом случае удалить, выделившуюся память?
B>
B>for(int i = 0; i < sizeof(ch2)/sizeof(ch2[0]); ++i)
B> delete [] ch2[i];
B>
В этом коде происходит потеря 3-х указатей
Указатели полученные через new затем стали указывать на статические строки
Естественно программа дожна падать на delete
По научному это называется dangling (подвешивание или потеря адреса памяти)
Кстати это одна из распространненых ошибок при работе с указателями в С
Есть еще aliasing (Псевдонимизация адреса)
Например
int *p = new int;
int *q = new int;
p = q; // начиная с этого момента p указывает на ту же область что и q
H>char *ch2[3];
H>ch2[0] = new char[8];
H>ch2[1] = new char[8];
H>ch2[2] = new char[8];
// а теперь мы дружно забиваем на выделенную память
// и присваиваем указатели на строковые литералы :(
H>ch2[0] = "0000000";
H>ch2[1] = "1111111";
H>ch2[2] = "2222222";
H>cout << ch2[0] << ch2[1] << ch2[2] <<endl;
H>вот как в этом случае удалить, выделившуюся память?
В этом случае — уже никак.
H>>>char *ch2[3];
H>>>ch2[0] = new char[8];
H>>>ch2[0] = "0000000";
К>>
H>А почему так нелься? Объясните пожалуйста!!
ch2[0] — это объект типа указатель.
Сначала ты в него записываешь указатель на размещенный оператором new блок памяти.
Но в следующей строке ты перезаписываешь это значение другим указателем — на статически (при компиляции) размещенный блок памяти, содержащий строковый литерал "0000000".
Прежнее значение оказывается безвозвратно утерянным и вернуть блок уже не удастся.
При попытке выполнить delete[] ch2[0] поведение программы не определено (скорее всего, просто упадет), т. к. блок памяти, на который указывает ch2[0] не был размещен при помощи new.