user3493435 user3493435 - 1 month ago 9
C++ Question

c++ removing of line from a text file (need explanations)

I am trying to remove a line containing username and password from text file.
so I tried to following,

1)Read the text file.

2)store the userName and password inside the textfile.

3)prompt the user which user he wants to delete.

4)the entire line will be deleted in the vector if the input matches with the username in the vector.

5)copy back to text file(not done yet)

I managed to get it by luck
textfile(userInfo.txt)

amber abc
janet def
chris DEF
gerald AZC


my output of my codes

Before Deletion
amber abc
janet def
chris DEF
gerald AZC

Enter User Name to delete: amber

After Deletion
janet def
chris DEF
gerald AZC


but what I don't really understand is, how my codes actually delete the entire line after it matches with my input espcially starting from

string name;
cout << "Enter User Name to delete: ";
cin >> name;
cout << " " << endl;
cout << "After Deletion" << endl;
for (int i =0; i<userDetails.size(); i++) {
if(userDetails[i].getUserName() == name){
userDetails.erase(userDetails.begin() + i);
}
cout << userDetails[i].getUserName() << " " << userDetails[i].getPassword() << "\n";
}


I hope someone can explain it to me.

my codes

user.h

#ifndef user_user_h
#define user_user_h
#include <iostream>

class user {

public:
user() {
userName = " ";
password = " ";
}

user(std::string userName,std::string password);

std::string getUserName();
std::string getPassword();

void setUserName(std::string userName);
void setPassword(std::string password);

private:
std::string userName,password;
};
#endif


main.cpp

#include "user.h"
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>

using namespace std;

user::user(string userName,string password) {
setUserName(userName);
setPassword(password);
};

string user::getUserName() {
return userName;
}
string user::getPassword() {
return password;
}

void user::setUserName(std::string userName) {
this->userName = userName;
}

void user::setPassword(std::string password) {
this->password = password;
}


int main(){
vector<user> userDetails;
string line;
string userName;
string password;

ifstream readFile("userInfo.txt");

while(getline(readFile,line)) {
stringstream iss(line);
iss >> userName >> password;

user userInfoDetails(userName,password);
userDetails.push_back(userInfoDetails);

}
readFile.close();

cout << "Before Deletion" << endl;
for (int i =0; i<userDetails.size(); i++) {
cout << userDetails[i].getUserName() << " " << userDetails[i].getPassword() << "\n";
}
cout << " " << endl;

string name;
cout << "Enter User Name to delete: ";
cin >> name;
cout << " " << endl;
cout << "After Deletion" << endl;
for (int i =0; i<userDetails.size(); i++) {
if(userDetails[i].getUserName() == name){
userDetails.erase(userDetails.begin() + i);
}
cout << userDetails[i].getUserName() << " " << userDetails[i].getPassword() << "\n";
}
}

Answer Source

I don't follow what you don't understand. However, I'm going to go over this snippet of code because it contains a serious bug that is common for beginners.

for (int i =0; i<userDetails.size(); i++) {
   if(userDetails[i].getUserName() == name){
        userDetails.erase(userDetails.begin() + i);
   }
    cout << userDetails[i].getUserName() << " " << userDetails[i].getPassword() << "\n";
}

The reason this "works" is that when your program encounters an object in the vector whose username matches the inputted username, you erase it from the vector. When this happens, the index of every object in the vector that is "further to the right" of the erased object gets shifted down by one. So suppose the matching name is "amber" (index 0). On the first loop iteration, amber will be deleted; janet shifts to index 0; chris shifts to index 1; and gerald shifts to index 2. Still on the first loop iteration, it will output index 0 which is now chris so everything is fine. On all the remaining loop iterations, userDetails.size() will evaluate to the new post-delete size, 3, so you will only get 3 iterations total, for indices 0, 1, 2.

NOW FOR THE BUG: Try entering "gerald" as the name to delete.

If you do this, remember that it will get processed on the fourth iteration of the loop, when i is 3. Gerald will be deleted, reducing the size to 3 and the last valid index to 2. But i is still 3 at this point! So

cout << userDetails[i].getUserName() << " " << userDetails[i].getPassword() << "\n";

on the fourth iteration is equivalent to:

cout << userDetails[3].getUserName() << " " << userDetails[3].getPassword() << "\n";

but there is no userDetails[3] and your program should segfault.

Here is how you would implement it properly:

for (int i =0; i<userDetails.size(); i++) {
   if(userDetails[i].getUserName() == name){
        userDetails.erase(userDetails.begin() + i);
        continue; // skip to next iteration because we can't guarantee 'i' is a valid
                  // index or that username on this iteration doesn't match
   }
    cout << userDetails[i].getUserName() << " " << userDetails[i].getPassword() << "\n";
}

However, I prefer the following idiom:

auto i = userDetails.begin(), e = userDetails.end();
while (i != e)
{
    if (name == i->getUserName())
    {
        i = userDetails.erase(i); // i is now equal to next element after deleted one
        e = userDetails.end();    // update e to be equal to the new end
        // don't output because we deleted
    }
    else
    {
        std::cout << i->getUserName() << ' ' << i->getPassword() << std::endl;
        ++i;
    }
}