Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки).
А>Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки).
потому что не надо юзать exit(0).
Как много веселых ребят, и все делают велосипед...
Здравствуйте, ononim, Вы писали:
А>>Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки). O>потому что не надо юзать exit(0).
ну замените на return 0;
все равно проблемы, первый деструктор не вызывается сразу же после первого конструктора, а лишь в конце, а соот-вено получаем ошибку
А>>>Почему не вызывается первый деструктор? То есть у меня получается в любом случае, что конструктор вызывается на один раз больше. Деструктор вызывается только если конструктор вызывался более одного раза. Эксперименты проводил разкомментирую строки). O>>потому что не надо юзать exit(0). S>ну замените на return 0; S>все равно проблемы, первый деструктор не вызывается сразу же после первого конструктора, а лишь в конце, а соот-вено получаем ошибку
Какие проблемы?
Деструктор локального объекта срабатывает когда исполнение выходит за пределы scope в котором он объявлен.
В случае exit(0) исполнение за пределы scope'а не выходит, потому деструктор не вызывается
Как много веселых ребят, и все делают велосипед...
а, вы про это..
Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу
Как много веселых ребят, и все делают велосипед...
Здравствуйте, ononim, Вы писали:
O>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу
Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д..
O>>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу S>Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д..
Выглядит просто — конструктор копирования/operator = по всем полям объекта.
Как много веселых ребят, и все делают велосипед...
Здравствуйте, ononim, Вы писали:
O>>>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу S>>Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д.. O>Выглядит просто — конструктор копирования/operator = по всем полям объекта.
Спасибо! Нашел также про "Правило трех". Вообщем проблема в том что копируются не данные, а указатели. При этом указатель может быть разрушен преждевременно. Вроде так.
O>>>>Оператор присваивания сделайте от TCHAR * нормальный. И еще оператор присваивания от roString и конструктор копирования от roString впридачу S>>>Наверное дурацкий вопрос, но. А можно как-то узнать как именно выглядит не явный конструктор копирования и оператор присваивания? Короче, может ли компилятор показать код, который он подставляет? Думаю вряд ли, но может хоть подскажете где почитать основные нюансы, связанные с этим. Если я не задал, то компилятор делает так или так, в таких или в таких случаях и т.д.. O>>Выглядит просто — конструктор копирования/operator = по всем полям объекта. S>Спасибо! Нашел также про "Правило трех". Вообщем проблема в том что копируются не данные, а указатели. При этом указатель может быть разрушен преждевременно. Вроде так.
Дык. Копирование/конструирование POD полей происходит путем т.н. побитового копирования с оригинала. Не POD — копируются/конструируются как могут.
Как много веселых ребят, и все делают велосипед...
1 — нет, но можно сделать метод InitFrom(const TCHAR* str, const size_t len) и вызывать его с обоих конструкторов.
2 — вроде как ничем (поправьте меня если чо ), но const roString & copy более общепринято.
3 — верно, за исключением формулировки — конструктор по умолчанию это тот который ваще без параметров. И в вашем исходном коде в присваивании вначале конструируется временный roString объект от TCHAК * И затем он присваивается в tr дефолтовым оператором присваивания. А вот если вы сделаете оператор присваивания от TCHAR * то избежите создания временного объекта.
Как много веселых ребят, и все делают велосипед...
Этот код изобилует проблемами.
Некоторые из них критические, некоторые не очень...
Поэтому, отвечая на исходный вопрос, заодно пробегусь и по смежным пунктам.
А>
А>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() и т.п. нужно использовать только в случаях крайней необходимости. Либо программа должна быть приспособлена к немедленному (а порой — внезапному) завершению.
Спасибо за такой тщательный разбор. Вот что я решил:
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.
Нашел ситуацию, когда целесообразно хранить пустую строку в куче, чем NULL-указатель. Если пытаемся при копировании использовать имеющуюся память, а не перераспределять память каждый раз. Получается что когда присваиваем пустую строку, то возможно будет выгодней просто первому символу в куче присвоить символ конца строки. Ну это если мы используем поле типа capacity. И тогда при последующем присвоении какой-либо строки, нам возможно не придется (если capacity хватает) выделять память заново.
Здравствуйте, 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 трактовалось как метазначение, то пришлось бы нагородить ветвлений.
Во-вторых, нужно отличать внешнее и внутреннее представление.
По большому счёту, внутреннее представление никого не заботит — делай так, как тебе удобнее. Но вот внешнее должно быть предсказуемо.
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? С целью уменьшить ветвления в коде? Тогда принимается.