34

I sit around and think about whether or not I should be using const char* or const std::string& pretty often when I'm designing class interfaces and when it comes down to it, I often feel like there's 6 in one hand with half a dozen in the other.

Take the following two function prototypes:

void foo(const char* str);
void foo(std::string str);

If the foo function were to store a string, I would say the second is a better option due to the ability to pass a string and utilize move semantics where possible. However, if foo only needed to read the string, would the const char* solution be better?

On a performance perspective, a temporary std::string wouldn't need to be created. However, calling the function with an already existing string as an argument looks obtrusive: foo(mystr.c_str()). Worse yet, if more advanced operations need to be done on the array at some point down the road or if a copy should be stored, the interface then has to change.

So my questions is this:

Are there well defined, either personal or otherwise, conventions that rule when std::string or const char* is a better choice? Furthermore, when starting a new project, is it best to be consistent with usage or just take whichever one fits the current chunk of code best?

vmrob
  • 2,966
  • 29
  • 40
  • 14
    One simple rule is this: if you are unsure, then you'd better use `std::string`. – Cheers and hth. - Alf Apr 30 '14 at 08:42
  • 7
    if you don't want to create a temporary string, use `void foo(const std::string &bar)` – Red Alert Apr 30 '14 at 08:43
  • 2
    @RedAlert wouldn't the call `foo("hello")` cause a temporary to be created using the `std::string` variant? – vmrob Apr 30 '14 at 08:44
  • 1
    string is designed for strings, const char * is only suitable for constant string literals. For string variables, using char * is a no-no. Speed is not going to matter 99.9% of the times, and if your string has 15 characters they must be stored _somewhere_ anyway, there is no magic that will let you save that storing space. Pass by reference does not duplicate the data and if you're passing your parameters by value all the time, you're doing it very bad. – Daniel Daranas Apr 30 '14 at 08:45
  • 8
    I'd **always** prefer `std::string`. It saves you so much hassle later on. Trust me. – lethal-guitar Apr 30 '14 at 09:12
  • I would opt for a `string_ref`-like class over either. – Simple Apr 30 '14 at 10:40
  • Related: http://stackoverflow.com/questions/8015355/are-c-strings-and-streams-buffer-overflow-safe (in fact, my answer there half-applies here too). – cHao Apr 30 '14 at 12:59
  • 1
    If development time and reliability are more important than performance and compactness. Which is to say, 99% of the time. Exceptions are things like inner loops of game engines, embedded programs that need to be small, kernel code where the standard library is unavailable... – user253751 May 01 '14 at 08:26
  • Other reason to use `std::string` is that it can store `'\0'`. – Zereges Sep 11 '17 at 08:36

7 Answers7

53

const char* is a throwback to C. I'd say that in decent C++, about the only use for it is in extern "C" APIs.

std::string has a number of advantages:

  1. It provides a constant-time size() function. Discovering the length of a const char* takes linear time.

  2. It is guaranteed to be valid. A const char* has to be checked for being null, and it is entirely possible to pass incorrect data - data missing a null terminator. Such an occurrence is pretty much guaranteed to result in a crash or worse.

  3. It is compatible with standard algorithms.

If you're worried about performance impacts of having to create a std::string to call the function, consider taking the approach the standard library uses - change the function to take a pair of iterators instead. You can then provide a convenience overload taking a const std::string& delegating to the iterator-pair one.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • I'm much less worried about performance than I am about the coherence of the interface as a whole. I find that code that's easier to read is also easier to debug and easier to optimize. – vmrob Apr 30 '14 at 08:49
  • 2
    `string` is better here as well. Compare `myFunc(const char*)` and `myFunc(string)`. – DarkWanderer Apr 30 '14 at 10:13
  • 2
    @DarkWanderer you mean `myFunc(std::string)`. – Miles Rout May 10 '14 at 02:42
  • No. `using std::string` in headers is a must. – DarkWanderer May 10 '14 at 06:35
  • @DarkWanderer _No._ Typing `using anyNamespaceOrType` in a header is a very bad idea, for reasons I hope I don't have to explain. It's misleading to recomend this, especially with unilateral words like "must". In fact, many people, including me, don't even bother `using std` anywhere. – underscore_d Apr 12 '16 at 20:45
  • 'Misleading' in this case is using overly broad statements. Please explain why `using std::string` is a bad idea (*not* `using namespace std`, but exactly `std::string`) – DarkWanderer Apr 12 '16 at 21:02
  • @DarkWanderer Familiarity and clarity. That's the primary reason. If I look into code and see `std::string`, it's immediately obvious what it is. If I see `string`, I have to wonder: is there a `using` somewhere? Or is it a `typedef` or a custom-provided class which perhaps imitates `std::string`, perhaps not? – Angew is no longer proud of SO Apr 13 '16 at 08:56
  • This is entirely subjective and does not constitute a "best practice" or something. I don't claim my position is absolute objective truth either, but then I'm not trying to present it as one. – DarkWanderer Apr 13 '16 at 08:59
  • @DarkWanderer "`using std::string` in headers is a must" looks *quite* strong to me. – Angew is no longer proud of SO Apr 13 '16 at 09:16
  • Well, I would have apologized for the *form* of my message. But the dogmatic & patronizing tone in the comment above ("bad idea for reasons I hope I don't have to explain") doesn't leave me with desire to, so - I'll leave it at that. – DarkWanderer Apr 13 '16 at 09:23
21

Just a few notes from personal experience. If you are working on a c++ project (as per the tag you added) stick to the std::string provided as much as you can. Do not try to reinvent the most basic of all structures, the almighty string. I saw several projects where they reinvented the basic string and afterwards spent months fine tuning it.

In case of your c++ project from the moment you have introduced a char* variable you fall back to standard C functions, such as strlen() and strcpy (just to name two ...). And from this point your project starts turning in a mess, with hand managed memory allocation, etc...

In case you need to interact with third party libraries which accept const char* as their parameter (and I suppose you trust those libraries - ie: you trust they do not const_cast away the constness to do evil things with your poor string) you can use std::string::c_str() to get the const char* out of your string.

In case you need to interact with libraries which have methods accepting char* I highly recommend you take a copy of your string's c_str() and use that as the ingoing parameter to the library (of course, do not forget to delete the extra copy).

Beside of these extra points I just subscribe to all three points from Angew's response.

Ferenc Deak
  • 34,348
  • 17
  • 99
  • 167
6

std::string should always be the first choice in c++. To avoid extra copy overhead you should almost always pass argument as reference and const reference whereever and whenever possible.

In that case your function signature would become like this

void foo(std::string &str);

and like this if argument is const

void foo(const std::string &str);

There are benifits of this const key word that you can look here

Abhishek Bansal
  • 5,197
  • 4
  • 40
  • 69
  • 6
    No, in C++11 if you need to copy the string anyway you should pass by value rather than const reference. – Siyuan Ren Apr 30 '14 at 08:48
  • Also, sometimes its not best to pass by reference for builtin, See http://stackoverflow.com/questions/5346853/c-why-pass-by-value-is-generally-more-efficient-than-pass-by-reference-for-bu – ilent2 Apr 30 '14 at 08:48
  • Yes it is Its only where and when possible.. as it improves API readibility – Abhishek Bansal Apr 30 '14 at 08:50
4

std::string is better than using raw char * where you have to manage allocations, deallocations and size related issue to be careful for any overflow, underflow, that you are not crossing the size boundary. Whereas std::string these are all taken care of through abstraction

Dr. Debasish Jana
  • 6,980
  • 4
  • 30
  • 69
4

Instead of using

void foo(std::string str); 

you could use

void foo(const std::string& str);

which would be equivalent to

void foo(const char* str);

in terms of usage, because no memory is allocated when passing a reference. But for strings in C++ I'd definitely use std::string. For random data or C compatible interfaces I wouldn't.

nobody
  • 19,814
  • 17
  • 56
  • 77
Theolodis
  • 4,977
  • 3
  • 34
  • 53
  • 2
    const std::string& may have to new data if a string literal is passed( needs to be created ). const char* may avoid this allocation at run time. – Ben Apr 30 '14 at 08:47
  • 1
    The only problem I ever have with using `const std::string&` is when I know I'll need a copy to be made. That's the rationale for choosing `std::string` – vmrob Apr 30 '14 at 08:48
  • @Ben a `char[]` is also created when calling a function with a string literal. Nothing you gain from using `char*` – Theolodis Apr 30 '14 at 08:49
  • @vmrob if you need a copy you can still construct a fresh string by calling `std::string(str)` with str beeing `const std::string& str` – Theolodis Apr 30 '14 at 08:51
  • 1
    @Theolodis Doing that would destroy the benefits of move semantics in c++11. – vmrob Apr 30 '14 at 08:55
  • 1
    @Theolodis basically, if you know you're going to copy it, you should use `std::string`. Call like `foo(some_func_that_returns_a_string())` would pass their result via move semantics directly into the next call. In a lot of cases, even the move can be elided. See http://stackoverflow.com/questions/3106110/what-is-move-semantics – vmrob Apr 30 '14 at 08:59
  • @Theolodis: Calling your third `foo` as `foo("Bar")` will not cause dynamic allocation, while calling the second one might (if no SSO is applied). The two certainly aren't equivalent. – Cactus Golov Apr 30 '14 at 09:05
3

If you want better handling of your say data (in your case it will be string) i'd say go with char *. It will give you better access to your string and memory utilization. But if you don't have to worry about performance and memory management issue than you can use std::string easily.

A J
  • 720
  • 1
  • 8
  • 11
  • That's a difficult position for me to endorse because most of the time, performance and memory management aren't important enough to warrant confusing code or potential programming bugs. – vmrob Apr 30 '14 at 18:39
  • Agree... but its upto coder how he/she writes the code [confusing or easily understandable]. Although if we look for bigger picture than memory utilization is more important [e.g. transferring data on n/w] – A J May 02 '14 at 06:35
1

Well, I would rephrase your question slightly. You should ask when character array should be used instead of std::string.

It is because you should always use string unless you cannot use it. So situations where you should use characters array instead of string:

  1. Using APIs that supports C so you cannot use string.
  2. Passing data between exe and dll. It is not recommended to exchange data types that have constructor and/or destructor between exe and dll

If you worry about performance, after compilation with all optimizations enabled string becomes similar to character array, maybe gives even better performance in some cases, for example if you need to get number of characters it contains.

Also about performance, you can always pass by reference, as other answers mentioned, in case you have to pass a pointer to character array, you can use method c_str() which returns const pointer to first character, if you have to pass pointer (not const pointer) you can (but actually shouldn't) pass it like this: &str[0].

ST3
  • 8,826
  • 3
  • 68
  • 92