Re: Не вызывается деструктор
От: Кодт Россия  
Дата: 04.01.10 18:59
Оценка: 10 (2)
Здравствуйте, Аноним, Вы писали:

А>Вот код:


Этот код изобилует проблемами.
Некоторые из них критические, некоторые не очень...
Поэтому, отвечая на исходный вопрос, заодно пробегусь и по смежным пунктам.

А>
А>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() и т.п. нужно использовать только в случаях крайней необходимости. Либо программа должна быть приспособлена к немедленному (а порой — внезапному) завершению.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.