31

std::string provides const char* c_str ( ) const which:

Get C string equivalent

Generates a null-terminated sequence of characters (c-string) with the same content as the string object and returns it as a pointer to an array of characters.

A terminating null character is automatically appended.

The returned array points to an internal location with the required storage space for this sequence of characters plus its terminating null-character, but the values in this array should not be modified in the program and are only granted to remain unchanged until the next call to a non-constant member function of the string object.

Why don't they just define operator const char*() const {return c_str();}?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dustin Getz
  • 21,282
  • 15
  • 82
  • 131
  • One thing to keep in mind is the `c_str()` simply returns a ptr to the `std::string`'s internal buffer. Thus the lifetime of the returned string is tied to the lifetime of the `std::string`. Forcing the programmer to use `c_str()` method helps (at least for me) to keep this in mind. – wcochran Aug 03 '20 at 20:40

7 Answers7

28

From the C++ Programming Language 20.3.7 (emphasis mine):

Conversion to a C-style string could have been provided by an operator const char*() rather than c_str(). This would have provided the convenience of an implicit conversion at the cost of surprises in cases in which such a conversion was unexpected.

user7116
  • 63,008
  • 17
  • 141
  • 172
  • 5
    Care to explain what would be the costly surprise? – ChrisW Jan 29 '09 at 15:52
  • 8
    1. When conversion to a C string causes a memory reallocation because std::string doesn't necessarily store a NULL terminator 2. When the std::string goes out of scope and you are left with a dangling pointer to freed memory – Clay Jan 29 '09 at 15:58
  • 3
    I think the short answer is with resolving overloads. Additionally std::string can hold a \0 character that doesn't mean the end of the std::string. – user7116 Jan 29 '09 at 16:02
  • 11
    It is a matter of overloads. "char *" doesn't behave like any sensible person would expect a string to, and it's useful to have expressions like "string_1 - string_2" or "string_1 - 2" be flagged as compiler errors rather than compile to nonsense. – David Thornley Jan 29 '09 at 20:40
  • 1
    @user7116 The problem with embedded `\0` also affects `.c_str()`, so forcing use of that doesn't make it any better. – Barmar Jul 03 '17 at 21:24
20

I see at least two problems with the implicit conversion:

  • Even the explicit conversion that c_str() provides is dangerous enough as is. I've seen a lot of cases where the pointer was stored to be used after the lifetime of the original string object had ended (or the object was modified thus invalidating the pointer). With the explicit call to c_str() you hopefully are aware of these issues. But with the implicit conversion it would be very easy to cause undefined behavior as in:

    const char *filename = string("/tmp/") + name;
    ofstream tmpfile(filename); // UB
  • The conversion would also happen in some cases where you wouldn't expect it and the semantics are surprising to say the least:

    string name;
    if (name) // always true
     ;
    name-2; // pointer arithmetic + UB
    These could be avoided by some means but why get into this trouble in the first place?
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
Chestal
  • 211
  • 1
  • 3
  • 9
    +1 for providing concrete examples. Anybody can say "Oh it's dangerous", but without examples it is hard to understand how. – Mark Ransom Jun 28 '10 at 15:16
  • 2
    That's not a great example, people try writing code like your first example now, run into a compiler error, and tag on `.c_str()` to the end of the `string(...)` call and wind up in the same situation. – Mahmoud Al-Qudsi Mar 14 '17 at 01:35
5

Because implicit conversions almost never behave as you expect. They can give surprising results in overload resolution, so it's usually better to provide an explicit conversion as std::string does.

jalf
  • 243,077
  • 51
  • 345
  • 550
5

The Josuttis book says the following:

This is for safety reasons to prevent unintended type conversions that result in strange behavior (type char * often has strange behavior) and ambiguities (for example, in an expression that combines a string and a C-string it would be possible to convert string into char * and vice versa).

Michael Kristofik
  • 34,290
  • 15
  • 75
  • 125
4

I addition to the rationale provided in the specification (unexpected surprises), if you're mixing C API calls with std::string, you really need to get into the habit of using the ::c_str() method. If you ever call a varargs function (eg: printf, or equivalent) which requires a const char*, and you pass a std::string directly (without calling the extraction method), you won't get a compile error (no type checking for varargs functions), but you will get a runtime error (class layout is not binary identical to a const char*).

Incidentally, CString (in MFC) takes the opposite approach: it has an implicit cast, and the class layout is binary-compatible with const char* (or const w_char*, if compiling for wide character strings, ie: "Unicode").

Evg
  • 25,259
  • 5
  • 41
  • 83
Nick
  • 6,808
  • 1
  • 22
  • 34
  • I work with g++ and it always flags passing any type of object as varargs argument with a warning and a message: if this piece of code gets run, the program will terminate unexpectedly. It is not mandated in the standard but should be flagged by most compilers – David Rodríguez - dribeas Jan 30 '09 at 00:16
  • I was always curious how CString got away with this - thanks for the insight. – Mark Ransom Jun 28 '10 at 15:12
2

That's probably because this conversion would have surprising and peculiar semantics. Particularly, the fourth paragraph you quote.

Another reason is that there is an implicit conversion const char* -> string, and this would be just the converse, which would mean strange behavior wrt overload resolution (you shouldn't make both implicit conversions A->B and B->A).

jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • i know the experts knew what they are doing, but it strikes me that they would make the string class so hard to use without good reason. – Dustin Getz Jan 29 '09 at 15:40
  • I wouldn't say "so hard to use". But if you find typing 7 characters hard, well... – jpalecek Jan 29 '09 at 15:49
  • If you find yourself converting to char* often, you probably need to rethink your design. – rmeador Jan 29 '09 at 16:39
  • 1
    @rmeador: it would be easier to avoid converting to char* if the *C++ standard library* accepted std::string filenames ... – Porculus Feb 15 '10 at 00:07
  • @Porculus: unfortunately, that would require the stream headers include the string headers, which would slow down compilation of some translation units a little. Personally I doubt anyone would notice, even in enterprise level thousand-app automated builds, but maybe I'm wrong and someone's measured a difference. – Tony Delroy Apr 21 '11 at 07:25
1

Because C-style strings are a source of bugs and many security problems it's way better to make the conversion explicitly.

hyperboreean
  • 8,273
  • 12
  • 61
  • 97