Не вызывается деструктор
От: Аноним  
Дата: 04.01.10 13:35
Оценка:
Вот код:

class roString
{
    private:
        TCHAR* m_str;
        size_t m_len;

    public:
        roString(TCHAR* str = NULL, size_t len = 0) 
        {
            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);
                } else
                    m_str = NULL;
            }

            TCHAR* nullText = TEXT("NULL_TEXT");
            printf(">>>+ m_str: '%ws', len=%d\n", ((m_str) ? m_str : nullText), m_len);
        };

        ~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*/)    {
                delete [] m_str;
                m_str = NULL;
                m_len = 0;
            }
        }

        TCHAR* Str() { return m_str; };
        size_t Len() { return m_len; };
};

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);
    // ...
}


Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки).
Re: Не вызывается деструктор
От: ononim  
Дата: 04.01.10 13:39
Оценка: +2
А>Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки).
потому что не надо юзать exit(0).
Как много веселых ребят, и все делают велосипед...
Re[2]: Не вызывается деструктор
От: sanx  
Дата: 04.01.10 14:19
Оценка:
Здравствуйте, ononim, Вы писали:

А>>Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки).

O>потому что не надо юзать exit(0).

ну замените на return 0;
все равно проблемы, первый деструктор не вызывается сразу же после первого конструктора, а лишь в конце, а соот-вено получаем ошибку
Re[2]: Не вызывается деструктор
От: sanx  
Дата: 04.01.10 14:23
Оценка:
например так:

roString t = TEXT("r1");;
t = TEXT("r22");

получаю:

>>>+ m_str: 'r1', len=2
>>>+ m_str: 'r22', len=3
<<<- m_str: 'r22', len=3
>>>+ m_str: 'r333', len=4
<<<- m_str: 'r333', len=4
<<<- m_str: 'Для продолжения нажмите любую клавишу . . .
Re[3]: Не вызывается деструктор
От: sanx  
Дата: 04.01.10 14:25
Оценка:
вместо:

roString t = TEXT("r1");;
t = TEXT("r22");

это:

roString t = TEXT("r1");;
t = TEXT("r22");;
t = TEXT("r333");

но не суть
Re[3]: Не вызывается деструктор
От: ononim  
Дата: 04.01.10 14:25
Оценка:
А>>>Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки).
O>>потому что не надо юзать exit(0).
S>ну замените на return 0;
S>все равно проблемы, первый деструктор не вызывается сразу же после первого конструктора, а лишь в конце, а соот-вено получаем ошибку
Какие проблемы?
Деструктор локального объекта срабатывает когда исполнение выходит за пределы scope в котором он объявлен.
В случае exit(0) исполнение за пределы scope'а не выходит, потому деструктор не вызывается
Как много веселых ребят, и все делают велосипед...
Re[3]: Не вызывается деструктор
От: ononim  
Дата: 04.01.10 14:39
Оценка: 2 (1)
S>
>>>>+ m_str: 'r1', len=2
>>>>+ m_str: 'r22', len=3
S><<<- m_str: 'r22', len=3
>>>>+ m_str: 'r333', len=4
S><<<- m_str: 'r333', len=4
S><<<- m_str: 'Для продолжения нажмите любую клавишу . . .
S>

а, вы про это..
Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу
Как много веселых ребят, и все делают велосипед...
Re[4]: Не вызывается деструктор
От: sanx  
Дата: 04.01.10 15:19
Оценка:
Здравствуйте, ononim, Вы писали:

O>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу


Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д..

Спасибо за ответы!
Re[5]: Не вызывается деструктор
От: ononim  
Дата: 04.01.10 15:24
Оценка:
O>>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу
S>Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д..
Выглядит просто — конструктор копирования/operator = по всем полям объекта.
Как много веселых ребят, и все делают велосипед...
Re[6]: Не вызывается деструктор
От: sanx  
Дата: 04.01.10 15:31
Оценка:
Здравствуйте, ononim, Вы писали:

O>>>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу

S>>Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д..
O>Выглядит просто — конструктор копирования/operator = по всем полям объекта.

Спасибо! Нашел также про "Правило трех". Вообщем проблема в том что копируются не данные, а указатели. При этом указатель может быть разрушен преждевременно. Вроде так.
Re[7]: Не вызывается деструктор
От: ononim  
Дата: 04.01.10 15:37
Оценка:
O>>>>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу
S>>>Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д..
O>>Выглядит просто — конструктор копирования/operator = по всем полям объекта.
S>Спасибо! Нашел также про "Правило трех". Вообщем проблема в том что копируются не данные, а указатели. При этом указатель может быть разрушен преждевременно. Вроде так.
Дык. Копирование/конструирование POD полей происходит путем т.н. побитового копирования с оригинала. Не POD — копируются/конструируются как могут.
Как много веселых ребят, и все делают велосипед...
Re[8]: Не вызывается деструктор
От: sanx  
Дата: 04.01.10 16:37
Оценка:
Если можете, то ответе на вопросы в комментариях в коде:

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <strsafe.h>

class roString
{
    private:
        TCHAR* m_str;
        size_t m_len;

    public:
        roString(const TCHAR* str = NULL, const size_t len = 0) 
        {
            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);
                } else
                    m_str = NULL;
            }

            TCHAR* nullText = TEXT("NULL_TEXT");
            printf(">>>+ m_str: '%ws', len=%d\n", ((m_str) ? m_str : nullText), m_len);
        };

        roString(roString const & copy)
        {
            // Вопрос 1
            // Могу ли я здесь сделать что то типа:
            // roString(copy.m_str, copy.m_len)??? 
            // Т.е. просто вызвать конструктор по умолчанию?
            // У меня не получается. По-моему, каким-то образом разрушается m_str, 
            //   после того как возвращаемся из конструктора по умолчанию. Хотя не знаю, но ошибка.
            if (copy.m_str && copy.m_len) {
                m_len = copy.m_len;
                m_str = new TCHAR[m_len + 1];
                StringCchCopy(m_str, m_len + 1, copy.m_str);
            } else {
                m_len = 0;
                m_str = NULL;            
            }
        }

        // Вопрос 2
        // Чем отличается это:
        // roString & operator = (roString const & copy)
        // от того что ниже (дело в const)? Ничем?
        roString & operator = (const roString & copy)
        {
            if (this != &copy) {                
                TCHAR* new_str = new TCHAR[copy.m_len + 1];
                StringCchCopy(new_str, copy.m_len + 1, copy.m_str);
                delete [] m_str;
                m_str = new_str;
                m_len = copy.m_len;
            }
            return *this;
        }

        ~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*/)    {
                delete [] m_str;
                m_str = NULL;
                m_len = 0;
            }
        }

        TCHAR* Str() { return m_str; };
        size_t Len() { return m_len; };
};

int _tmain(int argc, _TCHAR* argv[])
{
    // Вопрос 3
    // Это вызовет конструктор по умолчанию:
    roString tr = TEXT("r22");
    // А это оператор присваивания для TCHAR*
    roString tr2;
    tr2 = TEXT("r2332");
    // Верно?

    return 0;
}
Re[9]: Не вызывается деструктор
От: ononim  
Дата: 04.01.10 17:27
Оценка: 3 (1)
1 — нет, но можно сделать метод InitFrom(const TCHAR* str, const size_t len) и вызывать его с обоих конструкторов.
2 — вроде как ничем (поправьте меня если чо ), но const roString & copy более общепринято.
3 — верно, за исключением формулировки — конструктор по умолчанию это тот который ваще без параметров. И в вашем исходном коде в присваивании вначале конструируется временный roString объект от TCHAК * И затем он присваивается в tr дефолтовым оператором присваивания. А вот если вы сделаете оператор присваивания от TCHAR * то избежите создания временного объекта.
Как много веселых ребят, и все делают велосипед...
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() и т.п. нужно использовать только в случаях крайней необходимости. Либо программа должна быть приспособлена к немедленному (а порой — внезапному) завершению.
Перекуём баги на фичи!
Re[2]: Не вызывается деструктор
От: sanx  
Дата: 05.01.10 02:29
Оценка:
Спасибо за такой тщательный разбор. Вот что я решил:

1) Тут большенство просто моих ошибок, которые я постарался исправить;

2) Но есть кое-что, над чем вы мне просто посоветовали подумать.
Например строка с нулевой длинной. Я почему-то для себя решил что вместо [Указатель]->"/0", я буду делать [NULL-Указатель]. Мне кажется что так удобней, или могут быть какие нюансы? Хотя замечаю что некоторые функции НЕ присваивают null указателю, а заносят в буфер TEXT(""). Но тут понятно, я ведь им передал указатель на буфер и они не имеют право сделать Указатель = NULL, мне же нужно будет еще освободить память. Но если я сам выделяю память, так почему бы мне просто не выделять ее для пустой строки? Поэтому я решил что если мы передаем len = 0, то далее уже решим пустая ли строка с помощью указателя. Если он null то строка пустая. Далее, если он не null, но указывает на _T(""), то функция StringCchLength вернет нам нулевую длину, и мы опять же не будем выделять память. Мне кажется так удобней. Буду благодарен за ваше мнение!

3) И еще, зачем мне возвращать m_str?m_str:_T("") (пункт 4)? Ведь все функции прекрасно поймуть NULL в качестве аргумента? Или могут быть проблемы? Я этот класс пытаюсь сделать как readOnlyString, думаю что roString.Str() который вернет NULL вместо указателя на TEXT(""), не должно создавать проблем, или как?

4) По поводу конца строки, то вроде как StringCchCopy и StringCchCopyN вставляют символ ноль в конце. Ну я все же заменил на StringCchCopyN.
Re[2]: Не вызывается деструктор
От: sanx  
Дата: 05.01.10 05:58
Оценка:
Переделал, буду очень благодарен за ваши комментарии!

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <strsafe.h>

class roString
{
    private:
        TCHAR* m_str; // Всегда = NULL, если m_len = 0; Содержит символ конца строки
        size_t m_len; // Не учитывает символ конца строки

        inline size_t getLen(const TCHAR* str)
        {
            size_t len;
            // Не учитывает символ конца строки в len; Если str = NULL, не вернет S_OK
            if (StringCchLength(str, STRSAFE_MAX_CCH, &len) != S_OK) len = 0;
            return len;
        }

        inline void initFromStrN(const TCHAR* str, const size_t len)
        {
            // TODO: "!str ||" - лишнее, т.к. везде при вызове copyStrN ориентируемся на len?
            //   который в getLen уже предусматривает ситуацию !str, верно?
            //   А в copy.m_len все должно быть корректно.
            // TODO: roString(TEXT(""), 4); - не совсем хорошая ситуация, 
            //   зачем выделять буфер больше? Может проверять длину?
            //   Хотя в описании StringCchCopyN вроде говориться что не скопирует более длины, что логично :)
            if (/*!str || */!len) goto zero; // Знаю goto типа не гуд :)
            
            // TODO: Если ошибка new?
            TCHAR* new_str = new TCHAR[len + 1]; // + 1 для конца строки
            if (StringCchCopyN(new_str, len + 1, str, len) == S_OK) { // Вставляет конец строки
                m_str = new_str;
                m_len = len;
                printf(">>>+ m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
                return;
            }
            delete [] new_str;

        zero:
            m_str = NULL;
            m_len = 0;    
            printf(">>>+ m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
        }

    public:        
        // Не может содержать [m_str] -> TEXT(""), пустую строку заменяем на [m_str] = NULL
        // Если len = 0, пытаемся определить длину по символу конца строки
        // TODO: Могут ли быть проблемы если мы будем всегда [Указатель] -> TEXT("") менять на [Указатель] = NULL?
        roString(const TCHAR* str = NULL, const size_t len = 0) 
        {
            initFromStrN(str, ((len) ? len : getLen(str)));
        };

        // TODO: Узнать! До оператора присваивания выполняется какой-либо конструктор?
        // TODO: Как должен быть инициализирован объект, если при обработке оператора присваивания произошла ошибка?
        roString & operator = (const TCHAR* str)
        {            
            printf("<<<= m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
            // TODO: Почему советуют сперва выделить память под новые данные и получить их, потом удалять старую?
            delete [] m_str; // TODO: Делать ли это в случае ошибки в initFromStrN?                
            initFromStrN(str, getLen(str));
            return *this;
        };

        roString(const roString & copy)
        {
            initFromStrN(copy.m_str, copy.m_len);
        }

        roString & operator = (const roString & copy)
        {
            if (this != &copy) {
                printf("<<<= m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
                delete [] m_str;
                initFromStrN(copy.m_str, copy.m_len);                
            }
            return *this;
        }

        ~roString()
        {
            printf("<<<- m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);

            delete [] m_str;
            m_str = NULL;
            m_len = 0;
        }

        TCHAR* Str() const { return m_str; };
        size_t Len() const { return m_len; };
};

int _tmain(int argc, _TCHAR* argv[])
{
    roString s(TEXT("str"));
    roString s2(TEXT("str"), 2);
    s = s2;
    s2 = TEXT("tchar");
    roString s3 = s2;
    roString s4 = TEXT("tchar2");
    roString s5 = roString(TEXT(""), 4);

    return 0;
}
Re[2]: Не вызывается деструктор
От: sanx  
Дата: 05.01.10 10:20
Оценка:
Нашел ситуацию, когда целесообразно хранить пустую строку в куче, чем NULL-указатель. Если пытаемся при копировании использовать имеющуюся память, а не перераспределять память каждый раз. Получается что когда присваиваем пустую строку, то возможно будет выгодней просто первому символу в куче присвоить символ конца строки. Ну это если мы используем поле типа capacity. И тогда при последующем присвоении какой-либо строки, нам возможно не придется (если capacity хватает) выделять память заново.
Re[3]: Не вызывается деструктор
От: Кодт Россия  
Дата: 05.01.10 14:36
Оценка: 3 (1)
Здравствуйте, sanx, Вы писали:

S>1) Тут большенство просто моих ошибок, которые я постарался исправить;


S>2) Но есть кое-что, над чем вы мне просто посоветовали подумать.

S>Например строка с нулевой длинной. Я почему-то для себя решил что вместо [Указатель]->"/0", я буду делать [NULL-Указатель]. Мне кажется что так удобней, или могут быть какие нюансы? Хотя замечаю что некоторые функции НЕ присваивают null указателю, а заносят в буфер TEXT(""). Но тут понятно, я ведь им передал указатель на буфер и они не имеют право сделать Указатель = NULL, мне же нужно будет еще освободить память. Но если я сам выделяю память, так почему бы мне просто не выделять ее для пустой строки? Поэтому я решил что если мы передаем len = 0, то далее уже решим пустая ли строка с помощью указателя. Если он null то строка пустая. Далее, если он не null, но указывает на _T(""), то функция StringCchLength вернет нам нулевую длину, и мы опять же не будем выделять память. Мне кажется так удобней. Буду благодарен за ваше мнение!

Во-первых, есть смысл честно поддерживать на входе len=0 как пустую строку. Чтобы сделать единообразным код взятия подстроки (в т.ч. пустой) от непустой строки.
Например, мы хотим напечатать все разбиения строки на три подстроки.
LPCTSTR s = _T("hello");
size_t n = _tcslen(s);
for(size_t i=0; i!=n; ++i)
  for(size_t j=i; j!=n; ++j)
    _tprintf(_T("%u %u : '%s'+'%s'+'%s'\n"),
        i, j,
        roString(s+0,i-0).Str(),
        roString(s+i,j-i).Str(),
        roString(s+j,n-j).Str()
    );

Если бы len=0 трактовалось как метазначение, то пришлось бы нагородить ветвлений.
    _tprintf(_T("%u %u : '%s'+'%s'+'%s'\n"),
        i, j,
        0==i ? _T("") : roString(s+0,i-0).Str(),
        i==j ? _T("") : roString(s+i,j-i).Str(),
        j==n ? _T("") : roString(s+j,n-j).Str()
    );


Во-вторых, нужно отличать внешнее и внутреннее представление.
По большому счёту, внутреннее представление никого не заботит — делай так, как тебе удобнее. Но вот внешнее должно быть предсказуемо.

S>3) И еще, зачем мне возвращать m_str?m_str:_T("") (пункт 4)? Ведь все функции прекрасно поймуть NULL в качестве аргумента? Или могут быть проблемы? Я этот класс пытаюсь сделать как readOnlyString, думаю что roString.Str() который вернет NULL вместо указателя на TEXT(""), не должно создавать проблем, или как?


Далеко не все функции понимают NULL. Или понимают, но ведут себя иначе.
Например, printf — строка формата принципиально не может быть нулевой; а строка-аргумент (выводимая по %s) — в зависимости от рантайма NULL будет напечатан как "(null)" или приведёт к разным ошибкам защиты памяти.

Поэтому, либо твой класс должен поддерживать и пустую, и нулевую строки одновременно — и не требовать от пользователя ухищрений; или поддерживать только ненулевые строки.

Если поддерживаешь NULL — то можно договориться о такой семантике
— roString(NULL).Str() == NULL
— roString(_T("")).Str() == _T("") — я имею в виду не равенство указателей, естественно, а то, что объект отдаёт ненулевую пустую строку
— roString(_T("hello"),0).Str() == _T("")
— roString(_T("hello")).Str() == _T("hello") — вместо дефолтного значения len=PLEASE_MEASURE_THE_LENGTH (чем бы оно ни было — 0, ~0, 0xDEADBEEF) можно просто завести ещё один конструктор
— roString().Str() == ??? — тут нужно определиться, что будет — NULL или _T("").


При том, что внутреннее представление коротких строк может быть каким угодно.
Навскидку:
— для пустой строки — хранить NULL, возвращать _T("")
— для пустой и односимвольных строк — хранить указатель на статическую строку (размещённую не в куче, а в секции данных/констант)
— для коротких строк — держать короткий буфер прямо в теле объекта; размер объекта при этом увеличивается, зато уменьшается фрагментация кучи.

S>4) По поводу конца строки, то вроде как StringCchCopy и StringCchCopyN вставляют символ ноль в конце. Ну я все же заменил на StringCchCopyN.


Хорошо, пусть будут StringCchCopy(N). Я, правда, не понимаю, зачем использовать винапишные функции, когда есть стандартные — _tcscpy(n).
Только для толерантности к NULL? С целью уменьшить ветвления в коде? Тогда принимается.
Перекуём баги на фичи!
Re[3]: Не вызывается деструктор
От: Кодт Россия  
Дата: 05.01.10 14:52
Оценка: 2 (1)
Здравствуйте, sanx, Вы писали:

S>
S>#include <stdio.h>
S>#include <tchar.h>
S>#include <windows.h>
S>#include <strsafe.h>

S>class roString
S>{
S>    private:
S>        TCHAR* m_str; // Всегда = NULL, если m_len = 0; Содержит символ конца строки
S>        size_t m_len; // Не учитывает символ конца строки

        // Эта функция не относится к объекту - можно сделать её статическим членом класса, а то и вообще вынести вовне.
        // inline и так подразумевается для функций, определённых в объявлении класса
S>        inline size_t getLen(const TCHAR* str)
S>        {
S>            size_t len;
S>            // Не учитывает символ конца строки в len; Если str = NULL, не вернет S_OK
S>            if (StringCchLength(str, STRSAFE_MAX_CCH, &len) != S_OK) len = 0;
S>            return len;
S>        }

S>        inline void initFromStrN(const TCHAR* str, const size_t len)
S>        {
S>            // TODO: "!str ||" - лишнее, т.к. везде при вызове copyStrN ориентируемся на len?
S>            //   который в getLen уже предусматривает ситуацию !str, верно?
S>            //   А в copy.m_len все должно быть корректно.
S>            // TODO: roString(TEXT(""), 4); - не совсем хорошая ситуация, 
S>            //   зачем выделять буфер больше? Может проверять длину?
S>            //   Хотя в описании StringCchCopyN вроде говориться что не скопирует более длины, что логично :)

S>            if (/*!str || */!len) goto zero; // Знаю goto типа не гуд :)
            // Просто сделай две подфункции: InitEmptyStr(), InitFilledStr(LPCTSTR str, size_t len)
            
S>            // TODO: Если ошибка new?
            // В этом случае будет кинуто исключение std::bad_alloc.

S>            TCHAR* new_str = new TCHAR[len + 1]; // + 1 для конца строки
S>            if (StringCchCopyN(new_str, len + 1, str, len) == S_OK) { // Вставляет конец строки
S>                m_str = new_str;
S>                m_len = len;
S>                printf(">>>+ m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
S>                return;
S>            }
S>            delete [] new_str;

S>        zero:
S>            m_str = NULL;
S>            m_len = 0;    
S>            printf(">>>+ m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
S>        }

S>    public:        
S>        // Не может содержать [m_str] -> TEXT(""), пустую строку заменяем на [m_str] = NULL
S>        // Если len = 0, пытаемся определить длину по символу конца строки
S>        // TODO: Могут ли быть проблемы если мы будем всегда [Указатель] -> TEXT("") менять на [Указатель] = NULL?

        // Ещё одно TODO: разрулить ситуацию roString(NULL, 12345) - типа, какого чёрта?!
S>        roString(const TCHAR* str = NULL, const size_t len = 0) 
S>        {
S>            initFromStrN(str, ((len) ? len : getLen(str)));
S>        };

S>        // TODO: Узнать! До оператора присваивания выполняется какой-либо конструктор?
        // Что значит "выполняется какой-либо конструктор"? Конечно, выполняется.

S>        // TODO: Как должен быть инициализирован объект, если при обработке оператора присваивания произошла ошибка?
S>        roString & operator = (const TCHAR* str)
S>        {            
S>            printf("<<<= m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
S>            // TODO: Почему советуют сперва выделить память под новые данные и получить их, потом удалять старую?
S>            delete [] m_str; // TODO: Делать ли это в случае ошибки в initFromStrN?                
S>            initFromStrN(str, getLen(str));
S>            return *this;
S>        };

S>        roString(const roString & copy)
S>        {
S>            initFromStrN(copy.m_str, copy.m_len);
S>        }

S>        roString & operator = (const roString & copy)
S>        {
S>            if (this != &copy) {
S>                printf("<<<= m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);
S>                delete [] m_str;
S>                initFromStrN(copy.m_str, copy.m_len);                

                // Вместо этого, можно прибегнуть к более безопасному трюку
                roString temp(copy); // здесь может вылететь исключение
                swap(*this, temp); // обменять члены объектов (естественно, надо реализовать swap)
                // после чего temp со старым содержимым *this будет разрушен, а копия нового останется в *this.
S>            }
S>            return *this;
S>        }

S>        ~roString()
S>        {
S>            printf("<<<- m_str: '%ws', len=%d\n", ((m_str) ? m_str : TEXT("NULL_TEXT")), m_len);

S>            delete [] m_str;
            // Обнулять члены в деструкторе - благородно, но бесполезно. Объект-то будет - тю-тю!
S>            m_str = NULL;
S>            m_len = 0;
S>        }

S>        TCHAR* Str() const { return m_str; };
S>        size_t Len() const { return m_len; };
S>};

S>int _tmain(int argc, _TCHAR* argv[])
S>{
S>    roString s(TEXT("str"));
S>    roString s2(TEXT("str"), 2);
S>    s = s2;
S>    s2 = TEXT("tchar");
S>    roString s3 = s2;
S>    roString s4 = TEXT("tchar2");
S>    roString s5 = roString(TEXT(""), 4);

S>    return 0;
S>}
S>
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.