2

I have a C++ function, which constructs a std::string inside. I want it to return the C const char * of this string to the caller. I'm using c_str() to get the C string out, but then this can be deallocated after the string is returned because the resulting buffer is owned by the std::string.

Should I do something like return strdup(str.c_str())? That doesn't feel right.

(I'm a beginner to C++, so please excuse me if this question seems dumb).

user3702705
  • 21
  • 1
  • 2
  • Why the `const char*` return type? – erenon Jun 03 '14 at 11:02
  • 2
    This is in a library that will be linked against from straight C. – user3702705 Jun 03 '14 at 11:04
  • A C-String is a pointer. That needs to be purposely deconstructed. I would make no sense to me if `std::string` saved the string and if it got destructed it destructed the C-String as well. The C-String should still exist even after the original `std::string` object got destructed. – BrainStone Jun 03 '14 at 11:05
  • 1
    The pointer still exists after the `std::string` is destroyed - which is entirely useless. The memory it points _at_ is deallocated, so you can't really use it for anything - unless you copy it out as the OP correctly suggests. – Useless Jun 03 '14 at 11:08

3 Answers3

3

It's not a very good idea to allocate memory in one library and free it in another.

Consider passing an already allocated buffer to the function, if the size is known in advance, and just write to that buffer.

If the size is not known in advance, consider passing a callback function to your function, which receives the const char* as a parameter:

typedef void (*ResultCallback)( void* context, const char* result );

void Foo( ResultCallback resultCallback, void* context )
{
     std::string s = "....";
     resultCallback( context, s.c_str() );
}

The implementation of ResultCallback can allocate the needed memory and copy the buffer pointed to by result. I'm assuming C so I'm not casting to/from void* explicitly.

void UserCallback( void* context, const char* result )
{
    char** copied = context;
    *copied = malloc( strlen(result)+1 );
    strcpy( *copied, result );
}

void User()
{
    char* result = NULL;

    Foo( UserCallback, &result );

    // Use result...
    if( result != NULL )
        printf("%s", result);

    free( result );
}

This is the most portable solution and handles even the toughest cases where the size of the returned string cannot be known in advance.

Alex
  • 7,728
  • 3
  • 35
  • 62
3

The pointer returned by std::string::c_str is only valid as long as the corresponding std::string object is alive. So you can not return that pointer from your function. There is nothing wrong with that. If you want a copy you have to provide your own memory and copy the data over.

To accomplish that you can use several different methods.

The good ones care about ownership and use interfaces which are hard to misuse.


Version 1 (primitive):

char* foo() {
    std::string a = "blah";
    return strdup(a.c_str());
}

This looks easy enough, however who cares about deleting the memory allocated by strdup? It is very likely, that the user of your function will forget to call free.


Version 2 (C++11):

struct free_delete { void operator()(void* x) { free(x); } };
template<typename T> using unique_c_ptr = std::unique_ptr<T,free_delete>;

unique_c_ptr<char[]> foo() {
    std::string a = "blah";
    return unique_c_ptr<char[]>(strdup(a.c_str()));
}

auto p = foo();
char* cp = p.get(); // gets raw pointer

This uses a smart pointer to automatically delete the memory when out of scope. unique_ptr models "unique" ownership, meaning there will always be exactly one owner. If that is too restrictive you can also use shared_ptr.


Version 3 (C-style):

void foo(char* buffer, size_t size) {
     std::string a = "blah";
     strncpy(buffer, a.c_str(), size);
}

char buffer[128];
foo(buffer, 128);

In this version the caller is explicitly responsible for managing the memory. He can store the result wherever he likes. It has the downside, that the user must guess a good buffer size...

Danvil
  • 22,240
  • 19
  • 65
  • 88
2

Should I do something like return strdup(str.c_str())?

Yes you should, or copy into a buffer provided by the caller. Btw., in that case you need to return char* instead of const char*, because the caller must be able to free the string.

That doesn't feel right.

I know the feeling, but it's the way to go. C++ is compatible with C, but that doesn't there's no friction on the border between them.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836