Здравствуйте, Аноним, Вы писали:
А>Вот код:
Этот код изобилует проблемами.
Некоторые из них критические, некоторые не очень...
Поэтому, отвечая на исходный вопрос, заодно пробегусь и по смежным пунктам.
А>А>class roString
А>{
А> private:
А> TCHAR* m_str;
А> size_t m_len;
А> public:
А> roString(TCHAR* str = NULL, size_t len = 0)
// 1. Конструктор принимает на вход неконстантную строку.
// 2. Неразличимы строка с явной нулевой длиной и строка с автоопределением длины
А> {
А> if (!str) {
А> m_len = 0;
А> m_str = NULL;
А> } else {
А> if (!len)
А> StringCchLength(str, STRSAFE_MAX_CCH, &m_len);
А> else
А> m_len = len;
А> if (m_len) {
А> m_str = new TCHAR[m_len + 1];
А> StringCchCopy(m_str, m_len + 1, str);
// 3. А если в исходной строке нет концевого нуля в позиции len?
А> } else
А> m_str = NULL;
// 4. На входе была ненулевая пустая строка, а в контейнере - нулевая (см.ниже)
А> }
А> TCHAR* nullText = TEXT("NULL_TEXT");
А> printf(">>>+ m_str: '%ws', len=%d\n", ((m_str) ? m_str : nullText), m_len);
А> };
// 5. Нет конструктора копирования и оператора присваивания
А> ~roString()
А> {
А> TCHAR* nullText = TEXT("NULL_TEXT");
А> printf("<<<- m_str: '%ws', len=%d\n", ((m_str) ? m_str : nullText), m_len);
А> if (m_str/* && m_len*/) {
// 6. Проверка избыточна: delete[] нулевого указателя прекрасно работает.
А> delete [] m_str;
А> m_str = NULL;
А> m_len = 0;
А> }
А> }
А> TCHAR* Str() { return m_str; };
// 4. Было бы логично возвращать здесь m_str?m_str:_T("")
А> size_t Len() { return m_len; };
// 7. Эти функции, кстати, можно объявить как константные ;)
А>};
А>int _tmain(int argc, _TCHAR* argv[])
А>{
А> roString t = TEXT("r1");// = roString();
А> //roString t = roString(TEXT("qqq"), 0);
А> //t = roString(TEXT("asdfgh"), 3);
А> //t = roString(TEXT("12345"), 5);
А> //t = roString(NULL, 0);
А> exit(0);
// 8. exit() завершает программу "без лишних разговоров"
А> // ...
А>}
А>
А>Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки).
Нужно переформулировать вопрос: конструкторы и деструкторы каких объектов мы здесь видим?
У нас есть
— переменная t, которую мы сконструировали в первой строке main(), но не смогли разрушить (ибо exit())
— временные объекты, которые создаются, присваиваются и тут же разрушаются в следующих строках
— если компилятор глуп и ленив, то и в первой строке может возникнуть временный объект (формально он там есть, но компиляторы имеют право сделать оптимизацию — и делают её).
Теперь по остальным номерам.
1. Неконстантная строка как аргумент конструктора. Строковые литералы — "...", L"...", _T("...") имеют тип const char*, const wchar_t*, const TCHAR* соответственно, но для совместимости с языком Си они приводятся к неконстантным.
Чем плохо забивать на константность?
— если подать на вход roString честную константную строку (не строковый литерал, а что-то иное) — будет ошибка компиляции
— объявление гласит "я могу и, пожалуй, хочу менять содержимое исходной строки" — а в случае строкового литерала это выстрел в область констант, ведущий к разнообразным феерверкам; из кода видно, конечно, что ты не хочешь менять, — ну так объяви по-честному.
2. roString(s,2).Len()=2, roString(s,1)=1, roString(s,0)= внезапно, _tcslen(s).
Обычно для признака "длина не задана" выбирают значение, заведомо не могущее быть длиной строки. Например, -1 (и тип, соответственно, знаковый — int или ptrdiff_t) либо ~0 или какое-то ещё невероятно большое.
0 в роли такого признака плох: ведь бывают же строки нулевой длины, правда?
3. cout<<roString(_T("hello"),1).Str() => "heZZZZZZZZ", скопировали 2 символа, про концевой ноль забыли (надеялись, что он будет в позиции len исходной строки? тогда зачем вообще указывать длину?), при чтении можем уехать далеко вперёд по памяти.
Правильный подход — скопировать ровно m_len символов, а затем дописать m_str[m_len]=0
4. Конечно, ради пустой строки разумно не создавать односимвольный буфер на куче, а держать в объекте признак "строка — пустая" (в данном случае это нулевое значение длины и нулевой указатель). Но тогда получается расхождение: создавали roString(_T("")) из ненулевой строки, а получили roString(_T("")).Str() == NULL.
5. Если объект явлно управляет памятью, то его конструктор копирования и оператор присваивания должен играть по тем же правилам. В противном случае, компилятор по умолчанию создаёт конструктор копирования и оператор присваивания, тупо дублирующие члены из объекта-источника в объект-назначение. Получается, что m_str у обоих объектов будут указывать на один и тот же буфер. Соответсвенно, деструкторы обоих объектов дважды удалят один и тот же буфер, и будет феерверк.
6. Просто к сведению: delete и delete[] могут принимать нулевые указатели. Всё будет хорошо.
7. Константность — полезнейшая штука! И повод для углублённого изучения С++. А кое-где за отсутствие константности компилятор и ругаться будет. Не стану сейчас развёртывать тему...
8. exit() выполняет цепочку atexit-функций, в том числе — деструкторы статических объектов, после чего завершает программу.
Деструкторы же локальных объектов вызываются в рамках функции, где объекты объявлены. То есть, до деструктора t мы просто не дойдём.
Вообще, функции немедленного завершения — exit(), abort(), terminate(), ExitProcess() и т.п. нужно использовать только в случаях крайней необходимости. Либо программа должна быть приспособлена к немедленному (а порой — внезапному) завершению.