4

What is the modern way to do this? Headers like <cstring> are deprecated and "C-like" functions are banned by some coding styles. I have three approaches of doing the same thing. Which one would be most idiomatic of modern C++?

1. Use iterators and include the null terminator

{
    std::string test{"hello, world!"};
    char* output = new char[test.size() + 1];
    std::copy(test.begin(), test.end(),
        output);
    output[test.size() + 1] = '\0';
    std::cout << output;
    delete output;
}

2. Use c_str() which includes the null terminator

{
    std::string test{"hello, world!"};
    char* output = new char[test.size() + 1];
    std::copy(test.c_str(), test.c_str() + std::strlen(test.c_str()),
        output);
    std::cout << output;
    delete output;
}

3. Use std::strcpy

{
    std::string test{"hello, world!"};
    char* output = new char[test.size() + 1];
    std::strcpy(output, test.c_str());
    std::cout << output;
    delete output;
}

I don't want to look like a noob to an interviewer who say "Oh you use strcpy, you must be a C programmer".

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
user6364501
  • 41
  • 1
  • 2

3 Answers3

17

The modern safe pre C++17 way to obtain a contiguous buffer is std::vector.

std::string test{"hello, world!"};
std::vector<char> output(test.c_str(), test.c_str()+test.size()+1);
// use output.data() here...

Since C++17, std::string has a non-const data() overload.

std::string test{"hello, world!"};
char * p = test.data();
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
10

The right way is not using new[] in the first place. Use std::vector instead:

std::string temp {"Some string"};
std::vector<char> data (begin(temp), end(temp));
// If '\0' termination is required:
data.push_back('\0');

You can access they underlying buffer of data with data.data(). If you are concerned about a reallocation due to the push_back, you can std::vector::reserve enough space before assigning the range.

If you don't need an actually modifiable char array, you can skip the copy and just use the pointer returned by std::string::data or std::string::c_str directly.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
2

While the other answers offer good advice, and are the correct way to do things in almost all circumstances, there are some genuine cases where you might be forced to use a char* instead of a container. The most common case will be if you need to interface directly with C code, and the called C-method requires a non-const pointer (i.e. it will take ownership of the memory).

This is how I would make the conversion:

#include <string>
#include <cstdlib>

char* convert(const std::string& source)
{
    const auto result = (char*) std::malloc(source.length() + 1);
    source.copy(result, source.length());
    result[source.length()] = '\0';
    return result;
}

Then you have something like:

int main()
{
    const std::string foo {"hello"};
    auto p = convert(foo);
    some_nasty_c_method(p); // promises to free p
    // other stuff
}

As a rule of thumb, if you need to delete yourself, don't do this; use a container, or std::unique_ptr instead.

Daniel
  • 8,179
  • 6
  • 31
  • 56
  • What's wrong with `std::vector v(foo.begin(), foo.end()); some_nasty_c_method(v.data());`? – Christian Hackl May 21 '16 at 12:57
  • @ChristianHackl `std::vector::data` returns a `const` pointer. – Daniel May 21 '16 at 12:58
  • @Daniel: [One overload does but there also is a non-const overload.](http://en.cppreference.com/w/cpp/container/vector/data) It is true however, that a function that is to call `free` on a given pointer is required to get a pointer obtained via `malloc`. This is hopefully specified in the interface description and then there is no question which method should be used / is best, because you're bound to use `malloc` anyway. – Pixelchemist May 21 '16 at 14:26
  • 1
    @ChristianHackl: The problem with `std::vector v(foo.begin(), foo.end());` is the missing null character which is usually expected by legacy string-handling C code (if there is no size parameter in which case one could skip the '\0'). – Pixelchemist May 21 '16 at 14:28