Skip to content

8.3. string Streams

The sstream header defines three types to support in-memory IO; these types read from or write to a string as if the string were an IO stream.

The istringstream type reads a string, ostringstream writes a string, and stringstream reads and writes the string. Like the fstream types, the types defined in sstream inherit from the types we have used from the iostream header. In addition to the operations they inherit, the types defined in sstream add members to manage the string associated with the stream. These operations are listed in Table 8.5. They may be called on stringstream objects but not on the other IO types.

Table 8.5. stringstream-Specific Operations

CodeDescription
sstream strm;strm is an unbound stringstream. sstream is one of the types defined in the sstream header.
sstream strm(s);strm is an sstream that holds a copy of the string s. This constructor is explicit (§ 7.5.4, p. 296).
strm.str()Returns a copy of the string that strm holds.
strm.str(s)Copies the string s into strm. Returns void.

Note that although fstream and sstream share the interface to iostream, they have no other interrelationship. In particular, we cannot use open and close on a stringstream, nor can we use str on an fstream.

8.3.1. Using an istringstream

An istringstream is often used when we have some work to do on an entire line, and other work to do with individual words within a line.

As one example, assume we have a file that lists people and their associated phone numbers. Some people have only one number, but others have several—a home phone, work phone, cell number, and so on. Our input file might look like the following:

morgan 2015552368 8625550123
drew 9735550130
lee 6095550132 2015550175 8005550000

Each record in this file starts with a name, which is followed by one or more phone numbers. We’ll start by defining a simple class to represent our input data:

c++
// members are public by default; see § 7.2 (p. 268)
struct PersonInfo {
    string name;
    vector<string> phones;
};

Objects of type PersonInfo will have one member that represents the person’s name and a vector holding a varying number of associated phone numbers.

Our program will read the data file and build up a vector of PersonInfo. Each element in the vector will correspond to one record in the file. We’ll process the input in a loop that reads a record and then extracts the name and phone numbers for each person:

c++
string line, word;  // will hold a line and word from input, respectively
vector<PersonInfo> people; // will hold all the records from the input
// read the input a line at a time until cin hits end-of-file (or another error)
while (getline(cin, line)) {
    PersonInfo info;      // create an object to hold this record's data
    istringstream record(line); // bind record to the line we just read
    record >> info.name;  // read the name
    while (record >> word)        // read the phone numbers
        info.phones.push_back(word);  // and store them
    people.push_back(info); // append this record to people
}

Here we use getline to read an entire record from the standard input. If the call to getline succeeds, then line holds a record from the input file. Inside the while we define a local PersonInfo object to hold data from the current record.

Next we bind an istringstream to the line that we just read. We can now use the input operator on that istringstream to read each element in the current record. We first read the name followed by a while loop that will read the phone numbers for that person.

The inner while ends when we’ve read all the data in line. This loop works analogously to others we’ve written to read cin. The difference is that this loop reads data from a string rather than from the standard input. When the string has been completely read, “end-of-file” is signaled and the next input operation on record will fail.

We end the outer while loop by appending the PersonInfo we just processed to the vector. The outer while continues until we hit end-of-file on cin.

INFO

Exercises Section 8.3.1

Exercise 8.9: Use the function you wrote for the first exercise in § 8.1.2 (p. 314) to print the contents of an istringstream object.

Exercise 8.10: Write a program to store each line from a file in a vector<string>. Now use an istringstream to read each element from the vector a word at a time.

Exercise 8.11: The program in this section defined its istringstream object inside the outer while loop. What changes would you need to make if record were defined outside that loop? Rewrite the program, moving the definition of record outside the while, and see whether you thought of all the changes that are needed.

Exercise 8.12: Why didn’t we use in-class initializers in PersonInfo?

8.3.2. Using ostringstreams

An ostringstream is useful when we need to build up our output a little at a time but do not want to print the output until later. For example, we might want to validate and reformat the phone numbers we read in the previous example. If all the numbers are valid, we want to print a new file containing the reformatted numbers. If a person has any invalid numbers, we won’t put them in the new file. Instead, we’ll write an error message containing the person’s name and a list of their invalid numbers.

Because we don’t want to include any data for a person with an invalid number, we can’t produce the output until we’ve seen and validated all their numbers. We can, however, “write” the output to an in-memory ostringstream:

c++
for (const auto &entry : people) {    // for each entry in people
    ostringstream formatted, badNums; // objects created on each loop
    for (const auto &nums : entry.phones) { // for each number
        if (!valid(nums)) {
            badNums << " " << nums;  // string in badNums
        } else
            // ''writes'' to formatted's string
            formatted << " " << format(nums);
    }
    if (badNums.str().empty())      // there were no bad numbers
        os << entry.name << " "     // print the name
           << formatted.str() << endl; // and reformatted numbers
    else                   // otherwise, print the name and bad numbers
        cerr << "input error: " << entry.name
             << " invalid number(s) " << badNums.str() << endl;
}

In this program, we’ve assumed two functions, valid and format, that validate and reformat phone numbers, respectively. The interesting part of the program is the use of the string streams formatted and badNums. We use the normal output operator (<<) to write to these objects. But, these “writes” are really string manipulations. They add characters to the strings inside formatted and badNums, respectively.

INFO

Exercises Section 8.3.2

Exercise 8.13: Rewrite the phone number program from this section to read from a named file rather than from cin.

Exercise 8.14: Why did we declare entry and nums as const auto &?