Search code examples
c++iostream

C++ How do I make sure file is not overwritten between program runs when adding new contacts to a contact list?


I'm trying to create a program for a simple contact book which takes information about a contact from the user and saves it in a file. When I re-run the program and open the same file next time, I obviously want all the contacts I added previously to still be there and not be overwritten if I choose to add new contacts (or just exit the program without adding any new contacts for that matter). But I can't get the file to save properly. I can get contacts added to a map, but depending on where I put the saveToFile() the file always overwrites contacts at some point, either after adding a new contact or between program runs.

Here is my class for the contact book and function calls in main:

#include <iostream>
#include <fstream>
#include <map>
#include <sstream>

struct Contact {
   std::string name;
   std::string address;
   std::string email;
   std::string number;
   std::string bday;
   std::string other;
}

class ContactBook { 
   private:
   std::map<std::string, Contact> contacts; 

   public:
   void ContactBook::loadFromFile(const std::string filename) {
        std::ifstream file(filename);
        if (!file) {
            std::cout << "Unable to open file" << std::endl;
            return;
        }  
        
        std::string line;
        while (std::getline(file, line)) {
            std::istringstream iss(line);
            Contact contact;
            if (std::getline(iss, contact.name, '\n') &&
                std::getline(iss, contact.address, '\n') &&
                std::getline(iss, contact.email, '\n') &&
                std::getline(iss, contact.number, '\n') &&
                std::getline(iss, contact.bday, '\n') &&
                std::getline(iss, contact.other, '\n')) {
                    contacts[contact.name] = contact;
                }
        }
        file.close();
        std::cout << "File " << filename << " loaded" << std::endl;
    }
    
  void ContactBook::addContact(const Contact& contact) {
    contacts[contact.name] = contact;   
  } 

  void ContactBook::saveToFile(const std::string& filename) {
    std::ofstream file;
    file.open(filename);
    for (const auto& pair : contacts) {
        const Contact& contact = pair.second;
        file << contact.name <<  '\n' <<
            contact.address << '\n' <<
            contact.email << '\n' <<
            contact.number << '\n' <<
            contact.bday << '\n' <<
            contact.other << '\n';
            
    } file.close();
  }

 void ContactBook::printFile(const std::string& filename) {
    std::string line;
    std::ifstream file;
    file.open(filename);
    if (file.is_open()) {
        while(std::getline(file, line)) {
            std::cout << line << '\n';
        }
        file.close();
    }
  }
}


int main() {

    ContactBook contactBook;
    contactBook.load("contacts.txt");
    int alt;
    bool exit = false;
    
    while (!exit) {
    std::cout << "1 - Add contact" << '\n' <<
             "2 - Remove contact" << '\n' <<   //will add later
             "3 - Search for contact" << '\n' << //will add later
             "4 - Save and exit" << '\n' <<
             "5 - Print contact list" << std::endl;
    
    std::cin >> alt;
    
    switch(alt) {
        case 1: {Contact contact;
            std::cin.ignore();  
            std::cout << "Name: ";
            std::getline(std::cin, contact.name);
            std::cout << "Address: ";
            std::getline(std::cin, contact.address);
            std::cout << "Email: ";
            std::getline(std::cin, contact.email);
            std::cout << "Phone number: ";
            std::getline(std::cin, contact.number);
            std::cout << "Birthday: ";
            std::getline(std::cin, contact.bday);
            std::cout << "Other info: ";
            std::getline(std::cin, contact.other);
            
            contactBook.addContact(contact);
            }
            break;
        case 2: /*...*/
            break;
        case 3: /*..*/
            break;
        case 4: contactBook.saveToFile("contacts.txt");
            exit = true;
            break;
        case 5: contactBook.printFile("contacts.txt");
            break;
        default: std::cout << "invalid input" << std::endl;
    }
  }
  return 0;
}

As of now, if I add a contact, save, and re-run the program and choose print, the previously added contact will be printed correctly. However, if I save and exit a second time, then the next run the file will be empty. The first contact will also be gone if a add another contact during the same program run - if I save and exit the file will contain only the second contact.

Sorry if I'm making a super dumb and obvious mistake here - I'm a beginner. But I just can't figure it out and I've been stuck with this for hours to the point where I can't really focus on what I'm doing anymore, help much appreciated.


Solution

  • The way you are reading the file can never work, because by default std::getline() stops reading when it encounters '\n', so it can never output a std::string that has a '\n' in it. But you are expecting multiple fields to be on a single line delimited by '\n', which is nonsense.

    The 1st iteration of the while loop will read in only the 1st contact's name into the istringstream, then the subsequent if will fail to read in the remaining values since they don't exist in the istringstream. Then the next while iteration will read in the 1st contact's address into the istringstream, and the if will subsequently fail. And so on.

    To do what you are attempting, use a different delimiter instead, such as '\t', eg:

    void ContactBook::loadFromFile(const std::string filename) {
        std::ifstream file(filename);
        if (!file) {
            std::cout << "Unable to open file" << std::endl;
            return;
        }  
            
        std::string line;
        while (std::getline(file, line)) {
            std::istringstream iss(line);
            Contact contact;
            if (std::getline(iss, contact.name, '\t') &&
                std::getline(iss, contact.address, '\t') &&
                std::getline(iss, contact.email, '\t') &&
                std::getline(iss, contact.number, '\t') &&
                std::getline(iss, contact.bday, '\t') &&
                std::getline(iss, contact.other))
            {
                contacts[contact.name] = contact;
            }
        }
        file.close();
        std::cout << "File " << filename << " loaded" << std::endl;
    }
        
    void ContactBook::saveToFile(const std::string& filename) {
        std::ofstream file(filename);
        if (!file) {
            std::cout << "Unable to create file" << std::endl;
            return;
        }  
    
        for (const auto& pair : contacts) {
            const Contact& contact = pair.second;
            file << contact.name <<  '\t' <<
                    contact.address << '\t' <<
                    contact.email << '\t' <<
                    contact.number << '\t' <<
                    contact.bday << '\t' <<
                    contact.other << '\n';            
        }
        file.close();
        std::cout << "File " << filename << " saved" << std::endl;
    }
    

    Otherwise, if you want to use '\n' to delimit your fields, then you need to get rid of the std::istringstream and just read the lines from the std::ifstream directly, eg:

    void ContactBook::loadFromFile(const std::string filename) {
        std::ifstream file(filename);
        if (!file) {
            std::cout << "Unable to open file" << std::endl;
            return;
        }  
            
        Contact contact;
        while (std::getline(file, contact.name) &&
               std::getline(file, contact.address) &&
               std::getline(file, contact.email) &&
               std::getline(file, contact.number) &&
               std::getline(file, contact.bday) &&
               std::getline(file, contact.other))
        {
            contacts[contact.name] = contact;
        }
        file.close();
        std::cout << "File " << filename << " loaded" << std::endl;
    }
        
    void ContactBook::saveToFile(const std::string& filename) {
        std::ofstream file(filename);
        if (!file) {
            std::cout << "Unable to create file" << std::endl;
            return;
        }  
    
        for (const auto& pair : contacts) {
            const Contact& contact = pair.second;
            file << contact.name <<  '\n' <<
                    contact.address << '\n' <<
                    contact.email << '\n' <<
                    contact.number << '\n' <<
                    contact.bday << '\n' <<
                    contact.other << '\n';
        }
        file.close();
        std::cout << "File " << filename << " saved" << std::endl;
    }