3

In particular, how do the code check if memory for chars should be reallocated? Or how many chars the user entered? If I wanted to assign a C-string's value to my implementation of a string class I would probably do something like this

   String& operator=(String& to, const char *from)
   {
      if((strlen(from) + 1) > to.size) {
        if (to.str != NULL) {
          delete[] to.str;
          to.str = NULL;
        }
        to.size = strlen(from) + 1;
        to.str = new char[to.size];
      }
      strcpy(to.str, from);
      return to;
    }

Easy enough. But the operator>> of the std::string is really making me curious.

Anhil
  • 65
  • 1
  • 6
  • The source code to various C++ runtime libraries (say [GNU's libstdc++](http://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/)) is available. – user2864740 Oct 22 '13 at 23:23
  • Your code is not exception safe: if the allocate fails, `to.str` points to stale memory. In general, assignments are best implemented in terms of constructions: `T& T::operator=(S from) { T(from).swap(*this); return *this; }` (doesn't work with throwing move assignments, however). – Dietmar Kühl Oct 22 '13 at 23:29
  • @DietmarKühl won't "new" throw an exception if allocation fails? – Anhil Oct 22 '13 at 23:32
  • @Anhil: Exactly. If new does throw an exception, you're left with an object with a bad `to.str`. (You called delete on it, but never assigned it a value!) Dietmar's advice about putting `=` in terms of other constructors (see the copy-and-swap idiom) is good advice, and avoids the trouble with exceptions leaving behind corrupt objects. – Thanatos Oct 22 '13 at 23:38
  • @Anhil: yes, `new char[...]` may throw an exception but you can't recover the value of `to.str` (for a strongly exception safe assignment nothing would change) and you didn't set `to.str`, at least, to a value which avoids double deletion (that is, your code doesn't even provide the basic exception guarantee). That's the whole point of why your code is not exception safe. – Dietmar Kühl Oct 22 '13 at 23:43
  • @Thanatos: oh, right. Thanks. I should learn about this copy-and-swap idiom, never seen it before. – Anhil Oct 22 '13 at 23:45
  • @DietmarKühl: would `if(... && to.str != NULL)` and `to.str = NULL` (after delete) solve the issue? – Anhil Oct 22 '13 at 23:48
  • @Anhil: You would end up with an implementation which would implement the basic exception safety guarantee. It would classify as exception-safe but I consider an error to implement the basic exception-safety guarantee when the code could be strongly exception-safe, especially when it comes for free (or is even cheaper as is likely the case if you need additional checks otherwise). Check the [exception safety](http://en.wikipedia.org/wiki/Exception_safety) definition: you always target the strongest guarantee which can reasonably achieved. – Dietmar Kühl Oct 22 '13 at 23:55

2 Answers2

4

Fundamentally, the implementation looks something like this (ignoring the fact that both the streams and the string are templates):

std::istream& operator>> (std::istream& in, std::string& value) {
    std::istream::sentry cerberos(in);
    if (cerberos) {
        value.erase();
        std::istreambuf_iterator<char> it(in), end;
        if (it != end) {
            std::ctype<char> const& ctype(std::use_facet<std::ctype<char> >(in.getloc()));
            std::back_insert_iterator<std::string> to(value);
            std::streamsize n(0), width(in.width()? in.width(): std::string::max_size());
            for (; it != end && n != width && !ctype.is(std::ctype_base::space, *it); ++it, ++to) {
                *to = *it;
            }
        }
    }
    else {
        in.setstate(std::ios_base::failbit);
    }
    return in;
}

A reasonable implementation would probably use an algorithm which would process the content of the stream buffer's buffer segment-wise, e.g., to avoid the repeated checks and calls to is() (although for the std::ctype<char> it is really just applying a mask to an element of an array). In any case, the input operator wouldn't faff about allocating memory: a typical case "not my job".

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Wow! That code looks really great! Simply put, it loops through the input buffer and inserts chars at the end of the string till space/tab/newline is reached, right? – Anhil Oct 23 '13 at 00:22
  • @Anhil: Yes. Conceptually, this is how it works although an actual implementation will probably bypass the actual interface of both the stream buffer and the string in some form... – Dietmar Kühl Oct 23 '13 at 00:25
0

I believe it must use some sort of smart memory allocation management. If you are familiar with c, you would have seen the function realloc. My thought is that most container classes in the stl internally use some form of realloc to allocate more memory to themselves.

To to answer your question, the string class is typedef'd from another class: std::basic_string<char> which is basically an array of char. So internally it has reserved memory that can grow or shrink depending on the user's preferences or needs. And like I mentioned before this memory management is done in a way that is optimal and safe so that information is not lost.

If I were to implement std::cin >> std::string it will be in the form of a for loop that iterates through the array of char and assigns a value to each character in the array

smac89
  • 39,374
  • 15
  • 132
  • 179
  • Actually, `std::string` is **not** derived from `std::basic_string`! `std::string` is the same type as `std::basic_string`. `std::string` is a `typedef` or a using alias of `std::basic_string`. Also note that making sure the string is null-terminated is the job of the string class: the input operator is merely a client of that class. – Dietmar Kühl Oct 22 '13 at 23:46