C ++ строки манипуляции - PullRequest
       26

C ++ строки манипуляции

6 голосов
/ 10 ноября 2008

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

Чтобы было очень ясно, использование std :: string или equlivents не вариант - это полностью char *.

Итак: что мне нужно сделать, это очень просто и сводится к объединению строк. Во время выполнения у меня есть 2 класса.

Один класс содержит информацию «type» в форме базового имени файла.

в шапке:

char* mBaseName;

и позже, в .cpp загружается информация, переданная откуда-то еще.

mBaseName = attributes->BaseName;

2-й класс предоставляет информацию о версии в виде суффикса к базовому имени файла, это статический класс, который в настоящее время реализован так:

static const char* const suffixes[] = {"Version1", "Version", "Version3"}; //etc.

static char* GetSuffix()
{
    int i = 0;
    //perform checks on some data structures
    i = somevalue;
   return suffixes[i];
}

Затем во время выполнения базовый класс создает нужное имя файла:

void LoadStuff()
{
    char* suffix = GetSuffix();
    char* nameToUse = new char[50];
    sprintf(nameToUse, "%s%s",mBaseName,suffix);

    LoadAndSetupData(nameToUse);
}

И вы сразу видите проблему. nameToUse никогда не удаляется, утечка памяти.

Суффиксы представляют собой фиксированный список, но имена базовых файлов являются произвольными. Созданное имя должно сохраняться после окончания «LoadStuff ()», так как неясно, когда и как оно будет использовано впоследствии.

Возможно, я слишком беспокоюсь или слишком глуп, но код, аналогичный LoadStuff (), встречается и в других местах, поэтому его нужно решить. Это разочаровывает, так как я не знаю достаточно о том, как все работает, чтобы увидеть безопасное и «нехорошее» решение. В C # я бы просто написал:

LoadAndSetupData(mBaseName + GetSuffix());

и не нужно беспокоиться.

Любые комментарии, предложения или советы приветствуются.

Обновление

Проблема с кодом, который я вызываю LoadAndSetupData (), заключается в том, что в какой-то момент он, вероятно, действительно копирует имя файла и сохраняет его локально, но фактическое создание экземпляров асинхронно, LoadAndSetupData фактически помещает вещи в очередь, и в этот момент по крайней мере, он ожидает, что переданная строка все еще существует.

Я не контролирую этот код, поэтому не могу обновить его функцию.

Ответы [ 13 ]

3 голосов
/ 10 ноября 2008

При управлении памятью в C ++ важно помнить о владении. Если данные LoadAndSetupData не собираются вступать во владение строкой, то это все равно ваша ответственность. Поскольку вы не можете удалить его немедленно (из-за проблемы асинхронности), вам придется держаться за эти указатели до тех пор, пока вы не сможете удалить их.

Поддерживать пул строк, которые вы создали:

  • Если у вас есть момент времени, когда вы знаете, что очередь была полностью обработана, вы можете просто удалить все строки в пуле.
  • Если вы знаете, что все строки, созданные после определенного момента времени, были обработаны, то следите за тем, когда были созданы строки, и вы можете удалить это подмножество. - Если вы можете как-то выяснить, когда была обработана отдельная строка, просто удалите эту строку.

class StringPool
{
    struct StringReference {
        char *buffer;
        time_t created;
    } *Pool;

    size_t PoolSize;
    size_t Allocated;

    static const size_t INITIAL_SIZE = 100;

    void GrowBuffer()
    {
        StringReference *newPool = new StringReference[PoolSize * 2];
        for (size_t i = 0; i < Allocated; ++i)
            newPool[i] = Pool[i];
        StringReference *oldPool = Pool;
        Pool = newPool;
        delete[] oldPool;
    }

public:

    StringPool() : Pool(new StringReference[INITIAL_SIZE]), PoolSize(INITIAL_SIZE)
    {
    }

    ~StringPool()
    {
        ClearPool();
        delete[] Pool;
    }

    char *GetBuffer(size_t size)
    {
        if (Allocated == PoolSize)
            GrowBuffer();
        Pool[Allocated].buffer = new char[size];
        Pool[Allocated].buffer = time(NULL);
        ++Allocated;
    }

    void ClearPool()
    {
        for (size_t i = 0; i < Allocated; ++i)
            delete[] Pool[i].buffer;
        Allocated = 0;
    }

    void ClearBefore(time_t knownCleared)
    {
        size_t newAllocated = 0;
        for (size_t i = 0; i < Allocated; ++i)
        {
            if (Pool[i].created < knownCleared)
            {
                delete[] Pool[i].buffer;
            }
            else
            {
                Pool[newAllocated] = Pool[i];
                ++newAllocated;
            }
        }
        Allocated = newAllocated;
    }

    // This compares pointers, not strings!
    void ReleaseBuffer(char *knownCleared)
    {
        size_t newAllocated = 0;
        for (size_t i = 0; i < Allocated; ++i)
        {
            if (Pool[i].buffer == knownCleared)
            {
                delete[] Pool[i].buffer;
            }
            else
            {
                Pool[newAllocated] = Pool[i];
                ++newAllocated;
            }
        }
        Allocated = newAllocated;
    }

};
3 голосов
/ 10 ноября 2008

РЕДАКТИРОВАТЬ: Этот ответ не решает его проблему полностью - я сделал другие предложения здесь: C ++ манипулирование строками

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

Оригинальный ответ:

В C ++, если я не могу использовать стандартную библиотеку или Boost, у меня все еще есть такой класс:

template<class T>
class ArrayGuard {
  public:
    ArrayGuard(T* ptr) { _ptr = ptr; }
    ~ArrayGuard() { delete[] _ptr; }
  private:
    T* _ptr;
    ArrayGuard(const ArrayGuard&);
    ArrayGuard& operator=(const ArrayGuard&);
}

Вы используете это как:

char* buffer = new char[50];
ArrayGuard<char *> bufferGuard(buffer);

Буфер будет удален в конце области (при возврате или выбросе).

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

Будьте проще - если вам нужны более умные указатели, используйте Boost.

Это полезно, если 50 в вашем примере является переменной.

3 голосов
/ 10 ноября 2008

Теперь мы видим, что проблема в том, как очистить строку, которую вы создали и передали LoadAndSetUpData ()

Я предполагаю, что:

  1. LoadAndSetUpData () не создает свою собственную копию
  2. Вы не можете изменить LoadAndSetUpData (), чтобы сделать это
  3. Вам нужно, чтобы строка еще некоторое время существовала после того, как LoadAndSetupData () вернет

Вот предложения:

  1. Можете ли вы создать собственные объекты очереди для вызова? Они гарантированно будут вызваны после тех, которые используют вашу строку. Если это так, создайте события очереди очистки с той же строкой, которая вызывает delete [] для них

  2. Существует ли максимальное число, на которое вы можете рассчитывать. Если вы создали большой массив строк, могли бы вы использовать их в цикле и быть уверенными, что когда вы вернетесь к началу, будет нормально повторно использовать эту строку

  3. Сколько времени вы можете рассчитывать? Если это так, зарегистрируйте их для удаления где-нибудь и проверьте это через некоторое время.

Лучше всего было бы для функций, которые принимают char *, чтобы стать владельцем или копировать. Совместное владение труднее всего сделать без подсчета ссылок или сбора мусора.

2 голосов
/ 10 ноября 2008

Если вы должны использовать char *, то LoadAndSetupData () должен явно задокументировать, кому принадлежит память для char * после вызова. Вы можете сделать одну из двух вещей:

  1. Скопируйте строку. Возможно, это самая простая вещь. LoadAndSetupData копирует строку в некоторый внутренний буфер, и вызывающая сторона всегда отвечает за память.

  2. Передача права собственности. LoadAndSetupData () документирует, что он будет нести ответственность за в конечном итоге освобождение памяти для символа *. Звонящий не должен беспокоиться об освобождении памяти.

Я обычно предпочитаю безопасное копирование, как в # 1, потому что распределитель строки также отвечает за его освобождение. Если вы идете с # 2, распределитель должен помнить НЕ освобождать вещи, и управление памятью происходит в двух местах, которые я нахожу сложнее поддерживать. В любом случае явно документирует политику, чтобы вызывающий абонент знал, чего ожидать.

Если вы пойдете с # 1, взгляните на ответ Лу Франко, чтобы увидеть, как вы могли бы распределить символ [] в безопасном для исключения, который обязательно должен быть освобожден с помощью класса защиты. Обратите внимание, что вы не можете (безопасно) использовать std :: auto_ptr для массивов.

2 голосов
/ 10 ноября 2008

Поскольку вам нужно, чтобы nameToUse еще существовало после функции, вы застряли, используя new, и я бы вернул указатель на нее, чтобы вызывающая сторона могла «удалить» ее позже, когда она больше не нужна.

char * LoadStuff()
{
    char* suffix = GetSuffix();
    char* nameToUse = new char[50];
    sprintf("%s%s",mBaseName,suffix);

    LoadAndSetupData(nameToUse);
    return nameToUse;
}

тогда:

char *name = LoadStuff();
// do whatever you need to do:
delete [] name;
2 голосов
/ 10 ноября 2008

Так как std :: string не является опцией, по какой-то причине вы изучали умные указатели? См повышение

Но я могу только рекомендовать вам использовать std :: string.

Christian

1 голос
/ 10 ноября 2008

В этом случае нет необходимости выделять кучу. И всегда используйте snprintf:

char nameToUse[50];
snprintf(nameToUse, sizeof(nameToUse), "%s%s",mBaseName,suffix);
0 голосов
/ 10 ноября 2008

Вы можете объединить некоторые идеи здесь.

В зависимости от того, как вы модулировали свое приложение, может существовать метод (main?), Выполнение которого определяет область, в которой nameToUse определяется как локальная переменная фиксированного размера. Вы можете передать указатель (& nameToUse [0] или просто nameToUse) тем другим методам, которые должны заполнить его (так же передать размер), или использовать его, зная, что хранилище исчезнет, ​​когда выйдет функция с локальной переменной или Программа завершается любым другим способом.

Существует небольшая разница между этим и использованием динамического выделения и удаления (поскольку указатель, содержащий местоположение, должен управляться более или менее одинаково). Локальное распределение во многих случаях более прямое и очень недорогое, когда нет проблем с сопоставлением максимально требуемого времени жизни с продолжительностью выполнения конкретной функции.

0 голосов
/ 10 ноября 2008

Спасибо всем за ваши ответы. Я не выбрал один в качестве «ответа», так как нет конкретного решения этой проблемы, и лучшие дискуссии по этому вопросу все равно поддержаны мной и другими.

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

Для вашего интереса я решил пока "обмануть" мой выход из трудности. Я сказал, что базовые имена были произвольными, но это не совсем так. На самом деле это тоже ограниченный набор имен, просто ограниченный набор, который может измениться в какой-то момент, поэтому я пытался решить более общую проблему.

А пока я расширю «статическое» решение до суффиксов и построю таблицу возможных имен. Это очень "хакерский", но будет работать и, кроме того, избегает рефакторинга большого количества сложного кода, который я не могу.

Обратная связь была фантастической, большое спасибо.

0 голосов
/ 10 ноября 2008

Первое. Зачем вам нужно, чтобы выделенная строка сохранялась после конца LoadStuff ()? Есть ли способ, которым вы можете выполнить рефакторинг, чтобы удалить это требование.

Поскольку C ++ не предоставляет простой способ делать подобные вещи, большинство сред программирования используют набор указателей для указателей для предотвращения проблем удаления / освобождения. Поскольку вещи могут быть распределены / освобождены только один раз, необходимо четко понимать, кто «владеет» указателем. Некоторые примеры руководств:

1) Обычно человек, который выделяет строку, является ее владельцем, а также отвечает за освобождение строки.

2) Если вам нужно освободиться в функции / классе, отличном от того, в котором вы размещены, должна быть явная передача права собственности другому классу / функции.

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

В вашем конкретном случае это сводится к тому, что LoadStuff () должен удалить [] nameToUse, а вызываемая им функция должна сделать локальную копию.

Одно альтернативное решение: если nameToUse будет передано много мест и его необходимо сохранить в течение всей жизни программы, вы можете сделать ее глобальной переменной. (Это избавляет от необходимости делать множество копий.) Если вы не хотите загрязнять свое глобальное пространство имен, вы можете просто объявить его статическим локальным для функции:

static char *nameToUse = new char[50];
...