Разминка для мозгов: строка со счётчиком

будет записано в traceback.

что это за место, и как его использовать?

То есть придётся создавать два оператора? Один метод, а другой дружественный?

Можно не отвечать, уже осознал всю глупость вопроса :\

В данном случае макросы просто облегчают тестирование.

Надпись +++ initializations +++ выводит банальный cout )) Это я просто для себя вывожу как бы названия секций, в которых тестирую те или иные методы. В этой секции вивисекции подвергались конструкторы, в следующей — присваивания )) Когда объём выдачи большой — оно помогает ориентироваться.

По отладочным функциям, если чего непонятно, спрашивай. Но там вроде всё достаточно примитивно.

Когда выбрасывается исключение, можно посмотреть трассировку кода, который выбросил это исключение, найти место, откуда оно было выброшено.

Может я туплю, но мне кажется это излишним. В «полевых» условиях заниматься этим не будет времени или возможности. Или всё-таки есть способ определить от куда пришло исключение в этом коде?:

PascalString str = "this is string";
int *i;
try
{
    i = new int[/*тут находится такое большое число, что память не выделится*/];
    str += "aaaaaaa...aaaa";//тут находится строка такой длинны, что память опять не выделится.

}
catch ( bad_alloc )
{
    cout << "Bad alloc exeption!" << endl;
    //Неоднозначность. Что породило исключение?
    //выделение памяти или добавление строки?

}

По отладочным функциям, если чего непонятно, спрашивай. Но там вроде всё достаточно примитивно.

Мой мозг ещё примитивнее :)
Что делает вот эта штука в макросе?:

#define LOG(op) cout << "** executing : `" #op "` -> " << (op) << endl;

/*#op -> которая вот эта*/

Я так понял, что она выводит тип и имя объекта?

Может я туплю, но мне кажется это излишним.

При тестировании, стектрейсы очень помогают. В боевых условиях они обычно логируются, что помогает быстро выявить причину возникающих багов.

Исходя из того, что было выше, ставлю на обсуждение вопрос:
Нужен ли классу PascalString свой класс исключений?.

UPD

При тестировании, стектрейсы очень помогают

Возможно вы меня неправильно поняли. Я имел ввиду, не то, что стектрейсы не нужны. Я имел ввиду то, что разрулить эту ситуацию этим методом врядли получится.

Что делает вот эта штука в макросе? #op

Эта штука делает «строкафикацию» (stringification) аргумента макроса. Вольный перевод из доки по препроцессору:

Иногда может потребоваться преобразовать аргумент макроса в строковую константу. Параметры не заменяются внутри строковых констант, но в этом случае можно использовать оператор препроцессора «#». Когда перед параметром макроса стоит «#», препроцессор заменяет его на фактический параметр в виде текстового литерала, превращая его в строковую константу.

Первоисточник ищи здесь документация по GCC (кстати, очень рекомендую всем, кто использует gcc — матчасть надо знать).

Исходя из вышеизложенного,

#define LOG(op) cout << "** executing : `" #op "` -> " << (op) << endl;

// ...

LOG(a = 2 + 3);     // исходная строка программы

// будет преобразована препроцессором в
cout << "** executing : `" "a = 2 + 3" "` -> " << (a = 2 + 3) << endl;;

// затем три строковых литерала будут слиты в один:
cout << "** executing : `a = 2 + 3` -> " << (a = 2 + 3) << endl;;
// что и будет откомпилировано

// При исполнении на консоль будет выведено:
** executing : `a = 2 + 3` -> 5
// где 5 - результат выражения (a = 2 + 3)

Нужен ли классу PascalString свой класс исключений?

Думаю, что нужен. Но только для сугубо внутренних случаев (например, выход индекса за границу строки). Для распределения памяти есть свои исключения.

А в примере, который ты приводишь в качестве аргумента, в реальных условиях будет неважно для кого не хватило памяти. Главное — что её не хватило. IMHO конечно.

UPD Я бы вернул класс исключения в основной заголовочный файл класса. Пепелац без гравицапы не летает.

например, выход индекса за границу строки

std::out_of_range для чего тогда?

в реальных условиях будет неважно для кого не хватило памяти

в реальных условиях нужно будет думать что делать — завершать приложение, работать дальше как-нибудь или попытаться что-то намутить с памятью. Да и освободить то что уже выделили не помешает:

i = new int[...];
str += "aaaaaaa...aaaa";

если исключение произойдет в функции operator+=, то если сам класс построен правильно, то ничего страшного не произойдет с ресурсами, захваченными объектом. Но т.к. первое выделение new int[...] прошло успешно, то в блоке catch необходимо освободить этот ресурс, иначе будет утечка. Собственно, для решения таких проблем подходит RAII.

Версия 4.0 доступна здесь.

Ничего нового практически здесь не было добавлено, ничего не вырезано. Изменения в основном косметические. Единственное — я добавил метод, возвращающий C++-строку( класс string )
Класс исключений остался, потому что пока не ясно нужен ли он.

Но интересно не это. Интересен лог работы программы( файл main.cpp ). Вот он:

** executing : `PascalString ps_1 = "\n-They are dead, and you are next "`
- [0] - "\x0A-They are dead, and you are next "(34) : constructor : PascalString
( const char* )
** executing : `PascalString ps0 = "-There Any and Tom?" + ps_1`
- [1] - "-There Any and Tom?"(19) : constructor : PascalString( const char* )
- [1] - "-There Any and Tom?"(19) : operator+ ( const PascalString&, const Pasca
lString& ) - object ob1
- [0] - "\x0A-They are dead, and you are next "(34) : operator+ ( const PascalSt
ring&, const PascalString& ) - object ob2
- [2] - "<NULL>"(0) : constructor : PascalString()
- [2] - "-There Any and Tom?\x0A-They are dead, and you are next "(53) : operato
r+ ( const PascalString&, const PascalString& ) - object ret
- [1] - "-There Any and Tom?"(19) : destructor : ~PascalString()
----------------
ps0 =
-There Any and Tom?
-They are dead, and you are next
---------------
- [2] - "-There Any and Tom?\x0A-They are dead, and you are next "(53) : destruc
tor : ~PascalString()
- [0] - "\x0A-They are dead, and you are next "(34) : destructor : ~PascalString
()

По моим расчётам у ps0 должен вызываться конструктор копирования, потому что operator+ возвращает объект PascalString. Но в журнале событий вообще нигде нет конструктора копирования. По моим расчётам:


** executing : `PascalString ps0 = "-There Any and Tom?" + ps_1`
 - [1] - "-There Any and Tom?"(19) : constructor : PascalString( const char* ) //->Вот этот конструктор относится к строке "There Any and Tom?"
 - [1] - "-There Any and Tom?"(19) : operator+ ( const PascalString&, const Pasca
lString& ) - object ob1 //->Это опять та же строка
 - [0] - "\x0A-They are dead, and you are next "(34) : operator+ ( const PascalSt
ring&, const PascalString& ) - object ob2 //Это ps_1
 - [2] - "<NULL>"(0) : constructor : PascalString() //-> Остаётся объект ps0
 - [2] - "-There Any and Tom?\x0A-They are dead, and you are next "(53) : operato
r+ ( const PascalString&, const PascalString& ) - object ret//-> Но, как видно объект с индексом 2, это возвращаемая оператором + строка, то есть предыдущий конструктор это строка `PascalString ret` в исходном коде.
 - [1] - "-There Any and Tom?"(19) : destructor : ~PascalString() //-> Тут уже происходит удаление объекта содержащего "There Any and Tom?"

// Дальше вывод содержимого ps0 на экран

То есть объект ps0 вообще не инициализируется ничем, не вызывается даже оператор =, но тем не менее его содержимое вполне себе правильное. В чём причина?

Оптимизация включена?

Я попробовал прогнать твою программу под MS VC++ 2008 в режиме Debug (вся оптимизация выключена). У меня получился немного другой вывод:

** executing : `PascalString ps_1 = "\n-They are dead, and you are next "`
- [0] - "\x0A-They are dead, and you are next "(34) : constructor : PascalString( const char* )
** executing : `PascalString ps0 = "-There Any and Tom?" + ps_1`
- [1] - "-There Any and Tom?"(19) : constructor : PascalString( const char* )
- [1] - "-There Any and Tom?"(19) : operator+ ( const PascalString&, const PascalString& ) - object ob1
- [0] - "\x0A-They are dead, and you are next "(34) : operator+ ( const PascalString&, const PascalString& ) - object ob2
- [2] - "<NULL>"(0) : constructor : PascalString()
- [2] - "-There Any and Tom?\x0A-They are dead, and you are next "(53) : operator+ ( const PascalString&, const PascalString& ) - object ret
- [3] - "-There Any and Tom?\x0A-They are dead, and you are next "(53) : constructor : PascalString( const PascalString& )
- [2] - "-There Any and Tom?\x0A-They are dead, and you are next "(53) : destructor : ~PascalString()
- [1] - "-There Any and Tom?"(19) : destructor : ~PascalString()
----------------
ps0 = 
-There Any and Tom?
-They are dead, and you are next 
---------------
- [3] - "-There Any and Tom?\x0A-They are dead, and you are next "(53) : destructor : ~PascalString()
- [0] - "\x0A-They are dead, and you are next "(34) : destructor : ~PascalString()

Конструктор копии, как видишь, вызывается.

По моим расчётам у ps0 должен вызываться конструктор копирования, потому что operator+ возвращает объект PascalString

обыкновенная оптимизация возвращаемого значения.

ТС, пора бы уже дорасти до использования системы контроля версий. Не задолбало еще выкладывать rar-архивы в облако mail.ru? :-)

Вот, почитай: http://git-scm.com/book/ru

porshe получил интересный эффект %-O

Для чистоты эксперимента я немного изменил код в main():

    LOG2(PascalString ps_1 = "\n-They are dead, and you are next ");
    LOG2(PascalString ps_2 = "-There Any and Tom?");
    LOG2(PascalString ps0 = ps_2 + ps_1);

Я посмотрел что творится в ассемблерном коде — у меня челюсть отвисла. В функцию PascalString operator+ ( const PascalString&, const PascalString& ) передаётся три параметра: ссылка на ps_2, ссылка на ps_1 и ссылка на ps0(!). Причём передаются через регистры, а не через стек (!!). При выполнении кода

PascalString ret;

Конструктору для инициализации локальной переменной ret передаётся не адрес из фрейма вызова функции operator+, как должно было бы быть, а тот самый адрес ps0, переданный как третий (как бы лишний) параметр. Конструктор спокойно инициализирует экземпляр класса (который вообще-то принадлежит другой функции). Далее всё идёт как по писанному, поскольку ret — это теперь синоним ps0. Только оператор копирования не вызывается. Это ж глупо, копировать экземпляр класса самого в себя ))

Вот такая вот чума.

porshe, у тебя получился довольно приличный класс для работы с pascal-строками.

Несколько мелких замечаний по коду:

(1) Наверное Croessmah прав по поводу исключений. Лучше пользоваться стандартными классами исключений и не выдумывать велосипед.

(2) Метод c_str(char*) потенциально опасен. Лучше добавить ещё один аргумент: размер принимающего буфера. И копировать не больше длины буфера или не больше длины строки, с учётом того, что надо добавить завершающий '\0'.

(3) Подозреваю, что производительность метода string PascalString::cpp_str() const будет не лучшая, особенно на достаточно длинных строках. Перед копированием символов можно воспользоваться методом basic_string::reserve(size_type) для резервирования места под строку.

(4) Операторы сравнения лучше делать «друзьями»:

    LOG(ps1 > "123456");     // ok
    LOG("123456" < ps1);     // ERROR

См. Г.Шилдт «Полный справочник по С++» 2006г. стр. 331

(5) Набор операторов сравнения логически не сбалансирован. «Больше» и «меньше» работают только по длине, а «равно» «не равно» — ещё и по содержимому строки. Думаю, что анализ содержимого надо добавить и в больше-меньше. (Сортировку обычно проводят с использованием функции «больше» или «меньше». У тебя массив pascal-строк будет сортироваться только по длине. Что есть нехорошо.)

(6) Не хватает одного метода. Лог для твоего класса:

** executing : `ps0 = "test0-"`
- [3] - "test0-"(6) : constructor : PascalString( const char* )
- [0] - "test0-"(6) : operator= ( const PascalString& )
- [3] - "test0-"(6) : destructor : ~PascalString()

А вот так выглядит лучше:

** executing : `ps0 = "test0-"`
- [0] - "test0-"(6) : operator=(const char*)

У тебя при присваивании экземпляру класса константной с-строки (думаю, очень распространённая операция) сначала создаётся временный объект, потом выполняется оператор присваивания, потом удаляется временный объект.

Если к методам класса добавить PascalString& operator=(const char*), всё обойдётся значительно дешевле ))

(7) В качестве затравки для дальнейшего развития мысли могу подкинуть следующую идею. Сделать так, что бы объект класса PascalString мог резервировать место. Например:

PascalString ps;
for (int i = 0; i < 256; i++)
    ps[i] = char(i);

Что получим? Правильно, исключение.

А вот так всё будет нормально:

PascalString ps(256);         // сразу резервируем 256 байт для работы
for (int i = 0; i < 256; i++)
    ps[i] = char(i);

Логично добавить ещё один метод: void PascalString::realloc(int) — для изменения размера строки (усечения или увеличения).

В этом случае также можно сильно оптимизировать работу метода void get( istream&, int end = '\n');, который сейчас перераспределяет память при получении каждого символа.

Метод c_str(char*) потенциально опасен. Лучше добавить ещё один аргумент: размер принимающего буфера. И копировать не больше длины буфера или не больше длины строки, с учётом того, что надо добавить завершающий '\0'.

ИМХО, еще лучше выделять память так же под завершающий ноль и возвращать указатель на строку.

Подозреваю, что производительность метода string PascalString::cpp_str() const будет не лучшая, особенно на достаточно длинных строках. Перед копированием символов можно воспользоваться методом basic_string::reserve(size_type) для резервирования места под строку.

А нафига вообще такой метод нужен? Уже привязали свой класс жестко к std::string, да и если воспользоваться первым пунктом моего поста, то в нем вообще не будет необходимости, просто передаем указатель const char * , который вернет метод c_str, в конструктор std::string, а уж там всё само построится, как по волшебству.

ИМХО, еще лучше выделять память так же под завершающий ноль и возвращать указатель на строку.

Выделять в куче?

ИМХО, еще лучше выделять память так же под завершающий ноль и возвращать указатель на строку.

А вот так я бы не стал делать. Получается, что выделяется память внутри функции, а освобождаться должна — вне. Это не хорошо. Вызывающий код должен выделить память, а потом её освободить.

Получается, что выделяется память внутри функции, а освобождаться должна — вне.

зачем её освобождать?

const char * c_str() const noexcept
{
    buffer[len] = '\0' ;//<--mutable, либо хранить ноль всегда
    return buffer ;//<-- возвращаем указатель на нашу строку
}

Память так же выделена в конструкторе, а c_str возвращает указатель.

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

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.