4

The following code works on bidirectional streams and finds the record id from file and then replaces contents for that record from the file. But before overwriting the content, it shifts the put pointer to the position of the get pointer. Through tellp()and tellg() it is found that they both were already at the same position before shifting. But on removing the seekp() line the code does not overwrite the data.

Contents in data.txt:

123 408-555-0394
124 415-555-3422
263 585-555-3490
100 650-555-3434

Code:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    int inID = 263;
    const string& inNewNumber = "777-666-3333";
    fstream ioData("data.txt");

    // Loop until the end of file
    while (ioData.good()) {
        int id;
        string number;

        // Read the next ID.
        ioData >> id;

        // Check to see if the current record is the one being changed.
        if (id == inID) {
            cout << "get pointer position " << ioData.tellg() << endl; //Displays 39
            cout << "put pointer position " << ioData.tellp() << endl; //Displays 39
            ioData.seekp(ioData.tellg()); //Commenting this line stops code from working
            ioData << " " << inNewNumber;
            break;
        }

        // Read the current number to advance the stream.
        ioData >> number;
    }

    return 0;
}

Question:

What is the need of using seekp() to shift the position of the put pointer if it is already there, as the get and put pointers move together?

honk
  • 9,137
  • 11
  • 75
  • 83
Harshul Sharma
  • 359
  • 1
  • 11
  • 2
    As `fstream` is defined in terms of C facilities deep inside, this is relevant: http://stackoverflow.com/questions/1713819/why-fseek-or-fflush-is-always-required-between-reading-and-writing-in-the-read-w – Revolver_Ocelot Jun 13 '16 at 11:23
  • @ Revolver_Ocelot Thanks, I got such a refer nowhere – Harshul Sharma Jun 13 '16 at 11:51

2 Answers2

3

The question linked by @Revolver_Ocelot in the comments gives relevant information. The most important part is that you have to either flush or seek between read and write access. I therefore modified your code in the following way:

if (id == inID) {
    cout << "get pointer position " << ioData.tellg() << endl; //Displays 39
    cout << "put pointer position " << ioData.tellp() << endl; //Displays 39
    ioData.flush();
    cout << "get pointer position " << ioData.tellg() << endl;
    cout << "put pointer position " << ioData.tellp() << endl;
    ioData.seekp(ioData.tellg()); //Commenting this line stops code from working
    ioData << " " << inNewNumber;
    break;
}

This gives the following interesting output:

get pointer position 39
put pointer position 39
get pointer position 72
put pointer position 72

(Calling flush() doesn't actually resolve the problem. I just added it to your code in order to show you that it modifies the file pointer.)

My assumption on your original code is the following: If you write to your file after reading from it first, without calling seekp() in between, then the file pointer gets modified by the write command before the data is actually written to the file. I assume that the write command performs some kind of flushing and that this modifies the file pointer in a similar way as the flush() command that I added to your code.

When I ran the code above on my PC, the flush() command moved the file pointer to position 72. If we remove the seekp() command from your original code, I think that the write command will also move the file pointer to position 72 (or maybe another invalid position) before actually writing to the file. In this case writing fails, because position 72 is behind the end of the file.

Consequently, ioData.seekp(ioData.tellg()); is needed to ensure that the file pointer is set to the correct file position, because it can change when you switch between reading from and writing to your file without calling seekp().

The last paragraph of this answer gives some similar explanation.

Community
  • 1
  • 1
honk
  • 9,137
  • 11
  • 75
  • 83
2

It is because it's a rule of c++ bidirectional streams that if someone wants to shift from input operation to output operation. Then one must use seek() function to make such shift.

This functionality is borrowed from the core of c language as whenever someone uses a bidirectional stream then programmer may be working with two different buffers in which one buffer may be for input and another for output. Now synchronizing both the buffers would be a performance inefficient solution. As most of the time programmer may not need to use both the input and output functionality and program would be maintaining both the buffers for the programmer for no good reason.

So as an alternative to this, another solution was implemented to let programmer explicitly perform the flushing and other management by invoking seek() function.

Which means that seek() function that we often use does not simply repositions the file pointer but also updates the buffers and stream also.

See also why fseek or fflush is always required between reading and writing in the read/write "+" modes

Community
  • 1
  • 1
Harshul Sharma
  • 359
  • 1
  • 11