3

For educational purposes, I am using cstrings in some test programs. I would like to shorten strings with a placeholder such as "...".

That is, "Quite a long string" will become "Quite a lo..." if my maximum length is set to 13. Further, I do not want to destroy the original string - the shortened string therefore has to be a copy.

The (static) method below is what I come up with. My question is: Should the class allocating memory for my shortened string also be responsible for freeing it? What I do now is to store the returned string in a separate "user class" and defer freeing the memory to that user class.

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;
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
mats
  • 1,818
  • 3
  • 18
  • 26
  • 2
    Since you are using C++, why not use std::string for your return type? Then the memory management is much simpler. – Don Wakefield Jul 26 '09 at 22:19
  • I don't see anything in the OP saying that it is essential that only C strings are used. @Don's question is valid imo – jalf Jul 26 '09 at 22:33
  • Well, I do know there is std::string. But I like to agonize over the fine bits of the C language ;) It is just for personal purposes. – mats Jul 26 '09 at 22:35
  • 1
    strncpy, etc. are definitely _not_ the "fine bits" of C. You should have a look at libowfat to see some really fine bits of C. If you personally dislike the fundamental improvements of C++, your whole question is doomed. You should re-tag it since it's then no longer a question about C++. If you want to see fine solutions for your problems in C, please have a look at libowfat: http://www.fefe.de/libowfat/ – vog Jul 26 '09 at 22:46
  • @vog Retagged question. btw, "fine bits" was meant as sarcasm - anyway, thanks for the link. – mats Jul 26 '09 at 22:50

8 Answers8

6

The standard approach of functions like this is to have the user pass in a char[] buffer. You see this in functions like sprintf(), for example, which take a destination buffer as a parameter. This allows the caller to be responsible for both allocating and freeing the memory, keeping the whole memory management issue in a single place.

BJ Homer
  • 48,806
  • 11
  • 116
  • 129
  • 1
    This is the standard approach in C. However, in C++ the standard approach is to use std::string. – vog Jul 26 '09 at 22:36
  • I should have mentioned that I use pdcurses. The C++ parts are used because I'd like to have some OO-like "widgets" for the pdcurses library. +1 for "reminding" me of the C approach. – mats Jul 26 '09 at 22:43
5

In order to avoid buffer overflows and memory leaks, you should always use C++ classes such as std::string in this case.

Only the very last instance should convert the class into something low level such as char*. This will make your code simple and safe. Just change your code to:

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

When using that function in a C context, you simply use the cstr() method:

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

That's all!

In general, you should not program in C++ the same way you program in C. It's more appropriate to treat C++ as a really different language.

vog
  • 23,517
  • 11
  • 59
  • 75
2

I've never been happy returning pointers to locally allocated memory. I like to keep a healthy mistrust of anyone calling my function in regard to clean up.

Instead, have you considered accepting a buffer into which you'd copy the shortened string?

eg.

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

where short_text = buffer to copy shortened string, and shortSize = size of the buffer supplied. You could also continue to return a const char* pointing to short_text as a convenience to the caller (return NULL if shortSize isn't large enough to).

Alan
  • 13,510
  • 9
  • 44
  • 50
  • 1
    While this strategy avoids memory leaks, it contains the danger of buffer overflows if the caller doesn't take care, which is even worse than memory leaks. In C++ there is no need to risk that. Just use std::string. – vog Jul 26 '09 at 22:38
  • +1 for function prototype. I guess, reversing the positions of text and short_text to shortenWithPlaceholder(char* short_text, size_t shortSize, const char* text, size_t textSize) would be even more like the string.h functions. – mats Jul 26 '09 at 22:39
  • @vog: I understand the question was based around "education purposes..." – Alan Jul 26 '09 at 22:59
  • @Alan: I think that risking buffer overflows is also a bad idea for "education purposes". YMMV. – vog Jul 27 '09 at 00:46
2

Really you should just use std::string, but if you must, look to the existing library for usage guidance.

In the C standard library, the function that is closest to what you are doing is

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

So I'd go with this:

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

The caller is responsible for memory management - this allows the caller to use the stack, or a heap, or a memory mapped file, or whatever source to hold that data. You don't need to document that you used new[] to allocate the memory, and the caller doesn't need to know to use delete[] as opposed to free or delete, or even a lower-level operating system call. Leaving the memory management to the caller is just more flexible, and less error prone.

Returning a pointer to the destination is just a nicety to allow you to do things like this:

char buffer[13];
printf("%s", TextHelper::shortenWithPlaceholder(buffer, source, 12));
Eclipse
  • 44,851
  • 20
  • 112
  • 171
  • ++ Actually, it was this nicety that led me to the approach as shown in my question. Seems to me, it's not C-like to have niceties ;) – mats Jul 26 '09 at 22:47
1

The most flexible approach is to return a helper object that wraps the allocated memory, so that the caller doesn't have to worry about it. The class stores a pointer to the memory, and has a copy constructor, an assignment operator and a destructor.

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();

Most real programs use std::string for this purpose.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
0

In your example the caller has no choice but to be responsible for freeing the allocated memory.

This, however, is an error prone idiom to use and I don't recommend using it.

One alternative that allows you to use pretty much the same code is to change shortened to a referenced counted pointer and have the method return that referenced counted pointer instead of a bare pointer.

Jeff Leonard
  • 3,284
  • 7
  • 29
  • 27
0

Edit: No, I'm wrong. I misunderstood what you were trying to do. The caller must delete the memory in your instance.

The C++ standard states that deleting 0/NULL does nothing (in other words, this is safe to do), so you can delete it regardless of whether you ever called the function at all. Edit: I don't know how this got left out...your other alternative is placement delete. In that case, even if it is bad form, you should also use placement new to keep the allocation/deallocation in the same place (otherwise the inconsistency would make debugging ridiculous).

That said, how are you using the code? I don't see when you would ever call it more than once, but if you do, there are potential memory leaks (I think) if you don't remember each different block of memory.

I would just use std::auto_ptr or Boost::shared_ptr. It deletes itself on the way out and can be used with char*.

Another thing you can do, considering on how TextHelper is allocated. Here is a theoretical ctor:

TextHelper(const char* input) : input_(input), copy(0) { copy = new char[sizeof(input)/sizeof(char)]; //mess with later }
~TextHelper() { delete copy; }
jkeys
  • 3,803
  • 11
  • 39
  • 63
  • The function I use is static in TextHelper. Only makes things more C-like. I should probably have left out any C++ reference in my question, but the damage is done. – mats Jul 26 '09 at 22:55
0

There are two basic ways that I consider equally common: a) TextHelper returns the c string and forgets about it. The user has to delete the memory. b) TextHelper maintains a list of allocated strings and deallocates them when it is destroyed.

Now it depends on your usage pattern. b) seems risky to me: If TextHelper has to deallocate the strings, it should not do so before the user is done working with the shortened string. You probably won't know when this point comes, so you keep the TextHelper alive until the program terminates. This results in a memory usage pattern equal to a memory leak. I'd recommend b) only if the strings belong semantically to the class that provides them, similar to the std::string::c_str(). Your TextHelper looks more like a toolbox that should not be associated with the processed strings, so if I had to choose between the two, I'd go for a). Your user class is probably the best solution, given a fixed TextHelper interface.

Malte Clasen
  • 5,637
  • 1
  • 23
  • 28