2009-07-26 1 views
3

В учебных целях я использую cstrings в некоторых тестовых программах. Я хотел бы сократить строки с помощью заполнителя, такого как «...».Избегайте утечек памяти во время мутирования c-строк

То есть "Quite a long string" будет "Quite a lo...", если моя максимальная длина установлена ​​в 13. Кроме того, я не хочу уничтожать исходную строку - поэтому сокращенная строка должна быть копией.

Ниже приведен метод (статический), с которым я сталкиваюсь. Мой вопрос: Должен ли класс, выделяющий память для моей сокращенной строки, также отвечать за ее освобождение? Теперь я хочу сохранить возвращенную строку в отдельном «классе пользователя» и отложить освобождение памяти для этого класса пользователя.

const char* TextHelper::shortenWithPlaceholder(const char* text, size_t newSize) { 
    char* shortened = new char[newSize+1]; 

    if (newSize <= 3) { 
     strncpy_s(shortened, newSize+1, ".", newSize); 
    } 
    else { 
     strncpy_s(shortened, newSize+1, text, newSize-3); 
     strncat_s(shortened, newSize+1, "...", 3); 
    } 
    return shortened; 
} 
+2

Поскольку вы используете C++, почему бы не использовать std :: string для вашего типа возврата? Тогда управление памятью намного проще. –

+0

Пожалуйста, прочитайте OP. – jkeys

+0

Я ничего не вижу в OP, говоря, что очень важно использовать только строки C. Вопрос @ Домен действителен imo – jalf

ответ

6

Стандартный подход к функциям, подобным этому, заключается в том, чтобы пользователь проходил в буфере char []. Вы видите это в таких функциях, как sprintf(), например, которые принимают буфер назначения в качестве параметра. Это позволяет вызывающему абоненту отвечать за распределение и освобождение памяти, сохраняя проблему управления всей памятью в одном месте.

+1

Это стандартный подход в C. Однако в C++ стандартным подходом является использование std :: string. – vog

+0

Я должен был упомянуть, что я использую pdcurses. Части C++ используются, потому что я хотел бы иметь некоторые OO-подобные «виджеты» для библиотеки pdcurses. +1 для «напоминания» мне о подходе C. – msi

0

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

Это, однако, ошибка, с которой сталкивается идиома, и я не рекомендую ее использовать.

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

0

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

В стандарте C++ указано, что удаление 0/NULL ничего не делает (другими словами, это безопасно делать), поэтому вы можете удалить его независимо от того, вы вообще вызвали функцию. Редактировать: я не знаю, как это ушло. Ваша другая альтернатива - удаление места. В этом случае, даже если это плохая форма, вы также должны использовать новое место размещения, чтобы сохранить распределение/освобождение в одном месте (иначе несоответствие сделало бы отладку смешной).

Это, как вы используете код? Я не вижу, когда вы когда-либо называете это более одного раза, но если вы это сделаете, есть потенциальные утечки памяти (я думаю), если вы не помните каждый отдельный блок памяти.

Я бы просто использовал std::auto_ptr или Boost::shared_ptr. Он удаляет себя на выходе и может использоваться с char *.

Другая вещь, которую вы можете сделать, учитывая, как выделяется TextHelper. Вот теоретический ctor:

TextHelper(const char* input) : input_(input), copy(0) { copy = new char[sizeof(input)/sizeof(char)]; //mess with later } 
~TextHelper() { delete copy; } 
+0

Функция, которую я использую, является static в TextHelper. Только делает вещи более C-подобными. Вероятно, я должен был оставить любую ссылку на C++ в моем вопросе, но ущерб нанесен. – msi

2

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

Вместо этого вы считаете, что принимаете буфер, в который вы скопировали сокращенную строку?

например.

const char* TextHelper::shortenWithPlaceholder(const char* text, 
               size_t textSize, 
               char* short_text, 
               size_t shortSize) 

где short_text = буфер для копирования укороченной строки, а shortSize = размер буфера в комплекте поставки. Вы также можете продолжить возвращать const char*, указывая на short_text в качестве удобства для вызывающего абонента (возврат NULL, если shortSize не достаточно велик).

+1

Хотя эта стратегия позволяет избежать утечек памяти, она содержит опасность переполнения буфера, если вызывающий абонент не заботится, что еще хуже, чем утечки памяти. В C++ нет необходимости рисковать этим. Просто используйте std :: string. – vog

+0

+1 для прототипа функции. Я полагаю, меняя позиции текста и short_text, чтобы сократить WithPlaceholder (char * short_text, size_t shortSize, const char * text, size_t textSize), будет еще более похож на функции string.h. – msi

+0

@vog: Я понимаю, что вопрос основывался на «образовательных целях ...» – Alan

0

Есть два основных способа, которые я считаю одинаково распространенными: a) TextHelper возвращает строку c и забывает об этом. Пользователь должен удалить память. b) TextHelper поддерживает список выделенных строк и освобождает их при их уничтожении.

Теперь это зависит от вашего шаблона использования. b) представляется мне рискованным: если TextHelper должен освободить строки, он не должен делать этого до того, как пользователь выполнит работу с сокращенной строкой. Вероятно, вы не узнаете, когда придет эта точка, поэтому вы сохраняете TextHelper до тех пор, пока программа не завершится. Это приводит к тому, что шаблон использования памяти равен утечке памяти. Я бы рекомендовал b), только если строки принадлежат семантически классу, который их предоставляет, подобно std :: string :: c_str(). Ваш TextHelper больше похож на панель инструментов, которая не должна быть связана с обработанными строками, поэтому, если бы мне пришлось выбирать между ними, я бы пошел за a). Ваш пользовательский класс, вероятно, является лучшим решением, учитывая фиксированный интерфейс TextHelper.

1

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

class string_wrapper 
{ 
    char *p; 

public: 
    string_wrapper(char *_p) : p(_p) { } 
    ~string_wrapper() { delete[] p; } 

    const char *c_str() { return p; } 

    // also copy ctor, assignment 
}; 

// function declaration 
string_wrapper TextHelper::shortenWithPlaceholder(const char* text, size_t newSize) 
{ 
    // allocate string buffer 'p' somehow... 

    return string_wrapper(p); 
} 

// caller 
string_wrapper shortened = TextHelper::shortenWithPlaceholder("Something too long", 5); 

std::cout << shortened.c_str(); 

Большинство реальных программ используют std::string для этой цели.

+0

Моим первоначальным ответом было просто «Использовать std :: string», но вопрос говорит об этом в образовательных целях. –

+1

И «вспомогательный объект, который обертывает выделенную память», является справедливым описанием std :: string! –

2

Действительно, вы должны просто использовать std::string, но если вам нужно, посмотрите на существующую библиотеку для использования.

В стандартной библиотеке C, функция, которая находится ближе всего к тому, что вы делаете

char * strncpy (char * destination, const char * source, size_t num); 

Так что я бы с этим:

const char* TextHelper::shortenWithPlaceholder(
    char * destination, 
    const char * source, 
    size_t newSize); 

Вызывающий отвечает за управление памятью - это позволяет вызывающему пользователю использовать стек или кучу, или файл с отображением памяти, или любой другой источник для хранения этих данных. Вам не нужно документировать, что вы использовали new[] для выделения памяти, и вызывающему абоненту не нужно знать, использовать delete[], а не free или delete, или даже вызов на более низком уровне операционной системы. Оставляя управление памятью вызывающему абоненту просто более гибким и менее подверженным ошибкам.

Возвращаясь указатель к месту назначения является только тонкость, чтобы позволить вам делать такие вещи, как это:

char buffer[13]; 
printf("%s", TextHelper::shortenWithPlaceholder(buffer, source, 12)); 
+0

++ На самом деле, именно эта тонкость привела меня к подходу, как показано в моем вопросе. Кажется мне, это не C-нравится иметь тонкости;) – msi

5

Для того, чтобы избежать переполнения буфера и утечки памяти, вы должны всегда использовать классы C++, такие как std::string в этом случае.

Только последний экземпляр должен преобразовать класс в нечто низкое, например char*. Это сделает ваш код простым и безопасным.Просто измените свой код:

std::string TextHelper::shortenWithPlaceholder(const std::string& text, 
               size_t newSize) { 
    return text.substr(0, newSize-3) + "..."; 
}

При использовании этой функции в контексте C, вы просто используете cstr() метод:

some_c_function(shortenWithPlaceholder("abcde", 4).c_str());

Вот и все!

В общем, вы не должны программировать на C++ так же, как вы программируете на C. Более уместно рассматривать C++ как действительно другой язык.

+1

+1. Если вы пишете код на C++, тогда не используйте C-конструкции. – GManNickG