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

Это уже совсем как-то нехорошо получается. С копированием содержимого pascal-строки во внешний буфер я ещё могу согласиться, но модифицировать состояние объекта для возврата указателя на буфер...

Вообще, идея возвращать с-строку или string, в данном случае порочна. Здесь как бы сбивает с толку термин "Pascal-строка". На самом деле это не строка, хотя и может успешно выступать в качестве текстовой строки. Если быть точным, то то PascalString — это массив байт. И его надо рассматривать именно в этом ракурсе.

Содержимое PascalString может быть любым. Содержимое с-строки не должно содержать символ '\0' внутри строки, но этот символ обязан быть в конце строки. Здесь отличие на уровне идеи, а не реализации.

Это уже совсем как-то нехорошо получается. С копированием содержимого pascal-строки во внешний буфер я ещё могу согласиться, но модифицировать состояние объекта для возврата указателя на буфер... Вообще, идея возвращать с-строку или string, в данном случае порочна.

std::string чем не пример?

А в новом стандарте даже в векторе возвращается без проблем. Если клиент не дурак, то не будет пытаться её разрушить.
Да и вообще, такая строка годится только для внутренностей программы, а при использовании какого-нибудь API придется всё-равно выдавать указатель на буфер.

Да и вообще, такая строка годится только для внутренностей программы, а при использовании какого-нибудь API придется всё-равно выдавать указатель на буфер.

У-у-упс! А вот с этого места поподробнее.

А как же один из основных принципов ООП? Как с инкапсуляцией?

А как же один из основных принципов ООП? Как с инкапсуляцией?

А ничего, что C++ не соответствует концепции ООП? Или соответствует, но далеко не полностью.

У-у-упс! А вот с этого места поподробнее.

большинство API имеют си-шные интерфейсы, значит туда нужно будет всё равно передавать буфер. Будет ли эффективно выделять память при каждом чихе и копировать туда строку? В большинстве случаем нужно лишь чтение строки, а не запись в нее, а значит лучше будет просто вернуть указатель на константу.

Ребята, может мне только кажется, но это мой взгляд со стороны.
Все вообще начиналось с постановки задачи не использовать никаких библиотек.
Но как всегда cstring подвернулась под руку, а как же без cstdlib, а string — тут без нее совсем не обойтись. :)))
Может быть задача поставлена неправильно, а может не туда пошли. ХЗ?

Ребята, может мне только кажется, но это мой взгляд со стороны.
Все вообще начиналось с постановки задачи не использовать никаких библиотек.
Но как всегда cstring подвернулась под руку, а как же без cstdlib, а string — тут без нее совсем не обойтись. :)))
Может быть задача поставлена неправильно, а может не туда пошли. ХЗ?

Alf, cstring используется только для того, чтобы можно было создать метод cpp_str()( который, видимо придётся убрать ), больше ничего из этой библиотеки не используется, так что можно сказать, что её нет.

cstdlib вообще нигде не используется( использовалась в ранних версиях для выделения памяти, или выделение памяти тоже написать самому? :) ), я просто забыл её удалить. Поэтому не волнуйся :)

А ничего, что C++ не соответствует концепции ООП? Или соответствует, но далеко не полностью.

Во-первых, что ты имеешь ввиду под несоответствием С++ концепции ООП?

Во-вторых, концепция — это концепция. В духе ООП можно писать программы и на pure-C и на ассемблере. Концепция — это для человека, а не для инструмента.

большинство API имеют си-шные интерфейсы, значит туда нужно будет всё равно передавать буфер.

Я бы сказал даже сильнее: нет ни одного API (и, наверное, никогда не будет), который использовал бы класс PascelString ))

Частично согласен с твоим предложением. Наверное оптимальным решением будет метод:

const char *data() const { return str; }

В классе PascalString критичным элементом, отвечающим за непротиворечивость данных класса является len. И именно его модификацию нельзя допускать извне.

PS. Кстати, при разработке класса незаслуженно обойдён вниманием вопрос сериализации. porshe, вопрос к тебе, как к основному разработчику: как записать в файл объект типа PascalString, и как его оттуда (из файла) восстановить?

porshe, вопрос к тебе, как к основному разработчику: как записать в файл объект типа PascalString, и как его оттуда (из файла) восстановить?

Ну так организовать запись в поток, или же писать в файл

out << str.data() ;

как записать в файл объект типа PascalString, и как его оттуда (из файла) восстановить?

У меня есть перегруженный оператор << который работает с потоком вывода, и метод get, который работает с потоком ввода.

В бинарном режиме можно сначала записать количество байт, а потом уже сами байты.

Я тут внёс небольшие изменения, в результате появилась новая версия.( опять на облаке майл, да не обидится на меня selevit, большое ему спасибо за материал, я его потихоньку читаю ).

porshe,

как записать в файл объект типа PascalString, и как его оттуда (из файла) восстановить?

У меня есть перегруженный оператор << который работает с потоком вывода, и метод get, который работает с потоком ввода.

Не работает ((

    LOG2(PascalString ps20);
    LOG2(PascalString ps21("0123456789"));
    LOG2(PascalString ps22("qwe rty"));
    ps22[3] = '\0';

    cout << "\n+++ out to stream +++\n\n";

    const char *fname_t = "data.txt";

    ofstream ofst(fname_t, ios_base::out);
    LOG2(ofst << ps20 << ps21 << ps22);    // (1)
    //LOG2(ofst << ps20 << endl << ps21 << endl << ps22 << endl);    // (2)
    ofst.close();


    cout << "\n+++ input from stream +++\n\n";

    PascalString ps30, ps31, ps32;
    ifstream ifst(fname_t, ios_base::in);
    LOG2(ps30.get(ifst));
    LOG2(ps31.get(ifst));
    LOG2(ps32.get(ifst));
    ifst.close();

Если не помогать функции get() (вариант (1)), то совсем плохо.

Если помогать (вариант (2)), третья переменная восстанавливается неправильно (из-за '\0' в середине).

С оператором << опять таки вопрос возникает: как быть, если в массиве байт есть непечатные символы? Ты же наверное обратил внимание, как я извернулся при написании метода iLog(). Здесь надо принять решение как будет правильно: тупо выводить в поток «как есть» или делать преобразование непечатных символов. (Здесь я не настаиваю в правильности первого или второго решения.) Но в любом случае operator << и get() для сериализации не подходят.

В бинарном режиме можно сначала записать количество байт, а потом уже сами байты.

Это более здравая мысль. Но, я не нашёл реализации ((

Плюха, приводящая к вылету программы:

PascalString& PascalString::operator= ( const char *str )
{
    if ( this->str )
       delete []str;   // здесь должно быть this->str !!!
    len = _strlen( str );
    this->str = new char[len];
    _memcpy( this->str, str, len );
    #ifdef DEBUG_PASCAL_STRING
    iLog( "operator= ( const char* )" );
    #endif
    return *this;
}

Мораль: не называйте формальные аргументы так же, как и члены класса.

Тоже интересный код:

PascalString::PascalString( const char smb )
{
    len = 1;
    str = new char[len];  // <-
    str[len-1] = smb;     // <-
}

Компилятор, наверное, соптимизирует. Но зачем писать так?

Что будет, если вызовут с -1?

PascalString::PascalString( int size )
{
    len = size;
    str = new char[len];
}

И здесь void PascalString::resize ( int size ) аналогично.

В resize() порадовало:

        _memcpy( buffer, str, ( size > len )? len : len - (len - size) );

Вообще-то len - (len - size) <=> size, если раскрыть скобки ))

Операторы <, > ведут себя оригинально. Делается сравнение по короткой строке. Но для строк «123» и «12345», я бы сказал, что последняя больше.

Ещё ляпа:

bool operator== ( const PascalString &ob, const PascalString &ob2 )
{
    if ( ob.len != ob.len )   // <-
       return false;
    else
        return !(_memcmp( ob.str, ob2.str,  ( ( ob.len < ob2.len )? ob.len : ob2.len ) ) );
}

Ещё ляпа:

int _memcmp( const void *b1, const void *b2, int size )
{
    char *b1ptr, *b2ptr;
    b1ptr = (char*)b1;
    b2ptr = (char*)b2;
    for ( int i = 0; i < size; i++ )
    {
        if ( *b1ptr++ != *b2ptr++ )       // <-
            return *b1ptr - *b2ptr;       // <-
    }
    return 0;
}

Сравниваем одни значения, а возвращаем разность других значений.

Ты вообще-то тестировал свой код? Или как написалось, так и выложил?

Не работает ((

В последней версии( см. ниже ), где немного исправлен метод get(), результат получился тот же, что и у класса string из стандартной библиотеки. Это я к тому, что, ИМХО, забота о правильном чтении из файла должна ложиться на плечи пользователя.

Хотя можно сделать так же, как и в бинарном режиме( с входным и выходным потоком соответственно ), сначала записываем количество, а потом саму строку, для входного потока делать то же самое, только читать. В этом случае можно для потоков cin и cout сделать исключение, не выводить количество символов для потока cout и не спрашивать то же самое для cin.

С оператором << опять таки вопрос возникает: как быть, если в массиве байт есть непечатные символы? Ты же наверное обратил внимание, как я извернулся при написании метода iLog(). Здесь надо принять решение как будет правильно: тупо выводить в поток «как есть» или делать преобразование непечатных символов. (Здесь я не настаиваю в правильности первого или второго решения.) Но в любом случае operator << и get() для сериализации не подходят.

Показывать непечатаемые символы, мне кажется нехорошо. Потому что это уже и не строка вовсе а смесь строки с массивом байт. Если же рассматривать класс PascalString именно как строку, то тогда нужно печатать так, как есть. Если же выводить непечатаемые символы цифрами, то тогда придётся рассматривать класс как массив байт, но ведь по заданию у нас строка.

В бинарном режиме можно сначала записать количество байт, а потом уже сами байты.

Это более здравая мысль. Но, я не нашёл реализации ((

По-моему, это лучше сделать пользователю класса.

Ты вообще-то тестировал свой код? Или как написалось, так и выложил?

Да, как написалось, так и выложил :-[

Вот исправленный код.

Тема поднадоела? Тем более, что selevit запустил новую разминку ;-)

На закуску выкладываю свой PascalString. Не как образец, но как вариант. Тоже не без шероховатостей, но допиливать лень ))
Впрочем, вроде работает ))

По функциональности класс почти аналогичен реализации от porshe. Не реализовывал метод get(): считаю, что при необходимости вызывающий код может считать строку в буфер char* или в string, а потом передать классу в виде const char*. Зато добавлены методы для чтения и записи в двоичном режиме — read() и write() соответственно.

PasString.h

#ifndef __PasString_H__
#define __PasString_H__

#include <iostream>

#define LOG_DEBUG

using namespace std;

class PascalString {
public:
    PascalString();
    ~PascalString();
    PascalString(const PascalString& other);
    PascalString& operator=(const PascalString& other);

    PascalString(const char* cstr, int length = -1);
    explicit PascalString(int size);
    PascalString& operator=(const char* cstr);

    void realloc(int size);

    int size() const;
    const char* data() const;

    char& operator[](int index);
    char operator[](int index) const;
    PascalString& operator+=(const PascalString& op);

    ostream& write(ostream& os) const;
    istream& read(istream &is);

    friend bool operator==(const PascalString& op1, const PascalString& op2);
    friend bool operator!=(const PascalString& op1, const PascalString& op2);
    friend bool operator>(const PascalString& op1, const PascalString& op2);
    friend bool operator<(const PascalString& op1, const PascalString& op2);

    friend PascalString operator+(const PascalString& op1, const PascalString& op2);
    friend ostream& operator<<(ostream& os, const PascalString& p);

protected:
    static bool gt(const PascalString& op1, const PascalString& op2);
private:
    int len;
    char *str;

    void alloc(int size);

#ifdef LOG_DEBUG
    static int counter;
    int id;
    void iReg();
    void iLog(const char *comment) const;
#endif // LOG_DEBUG
};

class PascalStringException {
public:
    enum { BadIndex = 1, BadAllocSize, BadRead, BadWrite };
    int error_code;
    PascalStringException(int ec) : error_code(ec) {}
};

#endif // __PasString_H__

PasString.cpp

#include "PasString.h"

#include <iostream>
#include <iomanip>
#include <cstring>

using namespace std;

/****** Utility functions ***********************************************/

void cpystr(char *dest, const char *src, int size) {
    while (size-- > 0) {
        *dest++ = *src++;
    }
}

/****** Debugging methods ***********************************************/

#ifdef LOG_DEBUG

int PascalString::counter = 0;

void PascalString::iReg() {
    id = counter++;
}

void PascalString::iLog(const char *comment) const {
    cout << "- [" << id << "] - \"";
    if (str == NULL)
        cout << "<NULL>";
    else
        for (int i = 0; i < len; ++i)
            if (*(str + i) >= ' ')
                cout << *(str + i);
            else
                cout << "\\x" << hex << setw(2) << setfill('0') << (int)*(str + i) << dec;

    cout << "\"(" << len << ") : " << comment << endl;
}

#endif // LOG_DEBUG

/************************************************************************/

#ifndef LOG_DEBUG
#define iReg() ;
#define iLog(arg) ;
#endif // LOG_DEBUG


PascalString::PascalString() : len(0), str(NULL) {
    iReg();
    iLog("PascalString()");
}

PascalString::~PascalString() {
    iLog("destructor");
    if (str != NULL)
        delete [] str;
}

PascalString::PascalString(const PascalString& other) {
    len = other.len;
    str = new char[len];
    cpystr(str, other.str, len);

    iReg();
    iLog("PascalString(const PascalString&)");
}

PascalString& PascalString::operator=(const PascalString& rhs) {
    if (this == &rhs) return *this; // handle self assignment
    //assignment operator
    if (str != NULL)
        delete [] str;
    len = rhs.len;
    str = new char[len];
    cpystr(str, rhs.str, len);

    iLog("operator=(const PascalString&)");
    return *this;
}

PascalString& PascalString::operator=(const char* cstr) {
    if (str != NULL)
        delete [] str;
    len = strlen(cstr);
    str = new char[len];
    cpystr(str, cstr, len);

    iLog("operator=(const char*)");
    return *this;
}

PascalString::PascalString(const char* cstr, int length) {
    if (length < 0)
        len = strlen(cstr);
    else
        len = length;
    str = new char[len];
    cpystr(str, cstr, len);

    iReg();
    iLog("PascalString(const char*, int length)");
}

PascalString::PascalString(int size) : len(0), str(NULL)  {
    alloc(size);
    iReg();
    iLog("PascalString(int)");
}

void PascalString::alloc(int size) {
    if (size < 0)
        throw PascalStringException(PascalStringException::BadAllocSize);
    if (str != NULL)
        delete [] str;
    len = size;
    if (size > 0)
        str = new char[size];
    else
        str = NULL;
}

void PascalString::realloc(int size) {
    if (size < 0)
        throw PascalStringException(PascalStringException::BadAllocSize);
    if (size != len) {
        if (size == 0) {
            if (str != NULL)
                delete [] str;
            str = NULL;
            len = 0;
        }
        else {
            if (len == 0) {
                alloc(size);
            }
            else {
                // expand or shrink data buffer
                char *tmp = new char[size];     // new data buffer
                cpystr(tmp, str, (len < size ? len : size)); // copy data to new buffer
                len = size;
                delete [] str;                  // remove old buffer
                str = tmp;                      // change data buffer
            }
        }
    }
}

int PascalString::size() const {
    return len;
}

const char* PascalString::data() const {
    return str;
}


char& PascalString::operator[](int index) {
    if (index < 0 || index >= len)
        throw PascalStringException(PascalStringException::BadIndex);
    iLog("char& PascalString::operator[](int index)");
    return str[index];
}

char PascalString::operator[](int index) const {
    if (index < 0 || index >= len)
        throw PascalStringException(PascalStringException::BadIndex);
    iLog("char PascalString::operator[](int index) const");
    return str[index];
}

PascalString& PascalString::operator+=(const PascalString& op) {
    int sumlen = len + op.len;
    char *tmp = new char[sumlen];
    cpystr(tmp, str, len);
    cpystr(tmp + len, op.str, op.len);
    delete [] str;
    len = sumlen;
    str = tmp;

    iLog("operator+=(const PascalString&)");
    return *this;
}

ostream& PascalString::write(ostream& os) const {
    if (os.write((char *)&len, sizeof(len))) {
        if (len > 0) {
            if (!os.write(str, len))
               throw PascalStringException(PascalStringException::BadWrite);
        }
    }
    else
        throw PascalStringException(PascalStringException::BadWrite);
#ifdef LOG_DEBUG
    iLog("PascalString::write(ostream& os) const");
#endif // LOG_DEBUG
    return os;
}

istream& PascalString::read(istream &is) {
    int _len;
    char *_str = NULL;
    if (is.read((char *)&_len, sizeof(_len))) {
        if (_len > 0) {
            _str = new char[_len];
            if (!is.read(_str, _len))
                throw PascalStringException(PascalStringException::BadRead);
        }
    }
    else
        throw PascalStringException(PascalStringException::BadRead);

    if (str != NULL)
        delete [] str;
    str = _str;
    len = _len;

#ifdef LOG_DEBUG
    iLog("PascalString::read(istream &is)");
#endif // LOG_DEBUG
    return is;
}

bool PascalString::gt(const PascalString& op1, const PascalString& op2) {
    int minlen = op1.len < op2.len ? op1.len : op2.len;
    for (int i = 0; i < minlen; ++i) {
        if (op2.str[i] < op1.str[i]) {
            return false;
        }
    }
    return op1.len > op2.len;
}


/****** Friend functions ************************************************/


ostream& operator<<(ostream& os, const PascalString& p) {
    for (int i = 0; i < p.len; ++i)
        os << *(p.str + i);
#ifdef LOG_DEBUG
    p.iLog("operator<<(ostream& os, const PascalString& p)");
#endif // LOG_DEBUG
    return os;
}

PascalString operator+(const PascalString& op1, const PascalString& op2) {
    PascalString res;
    res.len = op1.len + op2.len;
    res.str = new char[res.len];
    cpystr(res.str, op1.str, op1.len);
    cpystr(res.str + op1.len, op2.str, op2.len);

#ifdef LOG_DEBUG
    res.iLog("operator+(const PascalString&, const PascalString&)");
#endif // LOG_DEBUG
    return res;
}

bool operator==(const PascalString& op1, const PascalString& op2) {
    if (op1.len != op2.len) {
        return false;
    }
    for (int i = 0; i < op1.len; ++i) {
        if (op1.str[i] != op2.str[i]) {
            return false;
        }
    }
    return true;
}

bool operator!=(const PascalString& op1, const PascalString& op2) {
    return !operator==(op1, op2);
}

bool operator>(const PascalString& op1, const PascalString& op2) {
    return PascalString::gt(op1, op2);
}

bool operator<(const PascalString& op1, const PascalString& op2) {
    return PascalString::gt(op2, op1);
}

PS. Было бы неплохо еще расставить noexcept (С++11) или throw(PascalStringException)...

Мне так показалось. В обсуждении нет былого энтузиазма ))

Некоторые задачи, типа разработки библиотечного класса, как правило, требуют тщательного тестирования и длительного допиливания. Что есть уже не творчество, а рутина.

Впрочем, я не против продолжить обсуждение.

Впрочем, я не против продолжить обсуждение.

И я не против, но по какой теме? :)

И я не против, но по какой теме? :)

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

Кстати, да. Многопоточность — вообще тема очень нужная, советую покопать в эту сторону porshe и другим интересующимся.

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

Ответить

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

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

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

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

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

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