Advertisement

C++ Deleting data from files

Started by January 05, 2016 04:06 AM
4 comments, last by xeyedmary 8 years, 11 months ago

Hello,

I finished writing a Phonebook application today, I just wanted to practice dealing with writing/reading/handling data from external files. All went well and the application works just like I want it too, but I was just wondering if my implementation is "the correct" way of for deleting specific portions of txt files. I wrote the function 2 different ways, one by creating a vector filled with Person objects that contains info from the contact list and I use those to fill the contact list file and the other way (which is commented out) was done by writing to a tmp txt file with the data minus what I want to delete and write over the original file with whats in the tmp file. I didn't like this way because I had the tmp contact file list lingering around.

Any advice on the proper way of handling this type of task or if you know a better way please let me know! Thanks!


void Phonebook::deleteEntry()
{
	int selection;
	bool valid = false;

	do
	{
		displayAllEntries();

		if (num_of_contacts < 1)
			break;

		std::cout << "\n\n\t\t\t\t\tEnter in the Entry to delete (0 = Cancel): ";
		std::cin >> selection;

		valid = std::cin.good();

		// making sure the selection was an int value
		if (!valid)
		{
			std::cin.clear();
			std::cin.ignore(INT_MAX, '\n');
		}
		else
		{
			// checks if user has contacts to delete and if the selection
			// is in range
			if ((selection <= num_of_contacts) && (num_of_contacts > 0) && (selection != 0))
			{
				std::vector<Person> contacts;
				std::ifstream reader("ContactList.txt", std::ios::in);

				if (reader.is_open())
				{
					// read data from ContactList and create Persons
					// to fill the fill the file after deletion
					// ignores the selected contact
					std::string fname, lname, email, phone_number;
					for (int i = 0; i < num_of_contacts; i++)
					{
						reader >> fname >> lname >> email >> phone_number;
                                                
                                                // i is always 1 less than the actual selection number
						if ((i + 1) != selection)
							contacts.push_back(Person(fname, lname, email, phone_number));
					}

					num_of_contacts--;
					reader.close();
				}
				else
				{
					std::cout << "\n\n\t\t\t\t\t*** [ Error! Cannot Open Contact List ] ***";
					std::cout << "\n\t\t\t\t\t"; system("PAUSE");
				}
				
				std::ofstream writer("ContactList.txt", std::ios::out);

				if(writer.is_open())
				{
					// Fills the file with the data from the contact vector
					for (int i = 0; i < num_of_contacts; i++)
					{
						writer << contacts[i].getFirstName() << " "
							   << contacts[i].getLastName() << " "
							   << contacts[i].getEmail() << " "
							   << contacts[i].getNumber() << std::endl;
					}

					std::cout << "\n\n\t\t\t\t\t----------------------------";
					std::cout << "\n\t\t\t\t\t|   Deletion Successfull   |";
					std::cout << "\n\t\t\t\t\t----------------------------";

					writer.close();
				}
				else
				{
					std::cout << "\n\n\t\t\t\t\t*** [ Error! Cannot Open Contact List ] ***";
					std::cout << "\n\t\t\t\t\t"; system("PAUSE");
				}

				// FILE READING/WITING TO TMP FILE IMPLEMENTATION
				/*std::ifstream reader("ContactList.txt", std::ios::in);
				std::ofstream writer("tmp_contacts.txt", std::ios::out);

				if (reader.is_open())
				{
					std::string fname, lname, email, phone_num;

					for (int i = 1; i <= num_of_contacts; i++)
					{
						if (i != selection)
						{
							reader >> fname >> lname >> email >> phone_num;
							writer << fname << " "
								   << lname << " "
								   << email << " "
								   << phone_num << std::endl;
						}
						else
						{
							reader >> fname >> lname >> email >> phone_num;
						}
					}

					num_of_contacts -= 1;

					reader.close();
					writer.close();

					writer.open("ContactList.txt", std::ios::out);
					reader.open("tmp_contacts.txt", std::ios::in);

					for (int i = 1; i <= num_of_contacts; i++)
					{
						reader >> fname >> lname >> email >> phone_num;
						writer << fname << " " 
							   << lname << " " 
							   << email << " "
							   << phone_num << std::endl;
					}

					std::cout << "\n\n\t\t\t\t\t----------------------------";
					std::cout << "\n\t\t\t\t\t|   Deletion Successfull   |";
					std::cout << "\n\t\t\t\t\t----------------------------";
				}
				else
				{
					std::cout << "\n\n\t\t\t\t\t*** [ Error! Cannot Open Contact List ] ***";
					std::cout << "\n\t\t\t\t\t"; system("PAUSE");
				}*/
			}
			else if (selection == 0)
			{
				std::cout << "\n\n\t\t\t\t\t-----------------------------------------";
				std::cout << "\n\t\t\t\t\t|   Canceling Request To Delete Entry   |";
				std::cout << "\n\t\t\t\t\t-----------------------------------------";
			}
			else
			{
				valid = false;

				std::cout << "\n\n\t\t\t\t\t*** [ Error! Invalid Selection ] ***";
				std::cout << "\n\t\t\t\t\t"; system("PAUSE");
			}
		}
	} while ((!valid) && (num_of_contacts != 0));
}

I wrote the function 2 different ways, one by creating a vector filled with Person objects that contains info from the contact list and I use those to fill the contact list file and the other way (which is commented out) was done by writing to a tmp txt file with the data minus what I want to delete and write over the original file with whats in the tmp file. I didn't like this way because I had the tmp contact file list lingering around.

Any advice on the proper way of handling this type of task or if you know a better way please let me know! Thanks!
You got both solutions, if you stick to a file-based storage. The obvious other candidate is to use some data base system for storing your phone records.

As for the tmp file approach that you don't like, that is commonly the used solution, I think.

The reasons for doing this is two-fold:

* Data safety: Suppose that while your write, or at least before the operating system has put it physically onto the disk, the power shuts down. Unless you have a PSU, the computer will stop somewhere in the middle of writing the file.

After restoring power, you are now left with a broken file. For 5 phone numbers that is not a big problem, but what if you enter the entire phone book in it?

If you first write the temporary file, and then rename the existing record file to .bak, and then rename the temporary file to the phone record file, The worst thing that can happen is that power shuts down between both rename operations. The program will be missing its record file, but at least it's still on the disk in one piece.

* Revert copy: Suppose I deleted some number, and then have second thoughts. If you rename the original file to .bak, I can rename that file back to the phone record file, and I have my previous phone numbers again.

Advertisement

I believe the most common way (not safest) is just overwriting the old data with the new data. I don't recall fstream having a specific method to delete specific content from a file. You need to load the file contents into memory and then modify it there.

The safest solution as Alberth pointed out is to load the file into memory and create an unaltered copy as a backup while writing the new data back to the original. That way if you want to revert a change as Alberth once again pointed out, you may do so.

You need to understand that filesystem operations are OS-specific and not a part of the C++ language. Not every C++ target even support filesystems, so there's a good reason for it.

From the point of view of C++, there are only data streams. Those data streams may or may not be attached to underlying persistent storage provided by the operating system. The C++ standard library provides a few rudimentary operations that may correspond to operations on the underlying storage, like fpos. If you want full control of the underlying storage, you need to write OS-specific code. For instance, positioning the record pointer in an ISAM file, which would be ideal for your application but sadly not supported by most of today's filesystems. You will probably also need to use the underlying OS read/write calls.

A POSIX OS provides os-level calls like open/creat/read/write/seek. Other OSes provide similar low-level calls (and often a POSIX-compatible layer if they're not POSIXlike to begin with).

Traditionally, for text files, the entire changed file is rewritten to a temporary file and the the underlying OS file manipulation calls are used on success to move the temp file onto the original file (a replace operation). C++17 is going to contain a filesystem library to help with that.

Notice that writing a temp file and then copying it, or writing a temp file to somewhere other than the target destination and renaming it from there, are bad ideas. A locked-down secure OS will often fail the rename or copy operation.

Stephen M. Webb
Professional Free Software Developer

You're right, I did not even think about crashes during the copying/writing to the file!


You got both solutions, if you stick to a file-based storage. The obvious other candidate is to use some data base system for storing your phone records.



As for the tmp file approach that you don't like, that is commonly the used solution, I think.

The reasons for doing this is two-fold:



* Data safety: Suppose that while your write, or at least before the operating system has put it physically onto the disk, the power shuts down. Unless you have a PSU, the computer will stop somewhere in the middle of writing the file.

After restoring power, you are now left with a broken file. For 5 phone numbers that is not a big problem, but what if you enter the entire phone book in it?

If you first write the temporary file, and then rename the existing record file to .bak, and then rename the temporary file to the phone record file, The worst thing that can happen is that power shuts down between both rename operations. The program will be missing its record file, but at least it's still on the disk in one piece.



* Revert copy: Suppose I deleted some number, and then have second thoughts. If you rename the original file to .bak, I can rename that file back to the phone record file, and I have my previous phone numbers again.

Very interesting, I did not know that about fstream, thanks for all the useful information!


You need to understand that filesystem operations are OS-specific and not a part of the C++ language. Not every C++ target even support filesystems, so there's a good reason for it.



From the point of view of C++, there are only data streams. Those data streams may or may not be attached to underlying persistent storage provided by the operating system. The C++ standard library provides a few rudimentary operations that may correspond to operations on the underlying storage, like fpos. If you want full control of the underlying storage, you need to write OS-specific code. For instance, positioning the record pointer in an ISAM file, which would be ideal for your application but sadly not supported by most of today's filesystems. You will probably also need to use the underlying OS read/write calls.



A POSIX OS provides os-level calls like open/creat/read/write/seek. Other OSes provide similar low-level calls (and often a POSIX-compatible layer if they're not POSIXlike to begin with).



Traditionally, for text files, the entire changed file is rewritten to a temporary file and the the underlying OS file manipulation calls are used on success to move the temp file onto the original file (a replace operation). C++17 is going to contain a filesystem library to help with that.



Notice that writing a temp file and then copying it, or writing a temp file to somewhere other than the target destination and renaming it from there, are bad ideas. A locked-down secure OS will often fail the rename or copy operation.

For the various reasons mentioned I would definitely avoid changing/editing a file in-place unless you're writing a database management system, in which case you've got a lot more code to write. Data serialization is an important topic; I wrote my own class (classes, really) implement it in my own apps because the standard answer ("just use boost") wasn't appropriate in this case. I do a lot of embedded work, so I also wrote a properties class to handle key - value pairs for embedded projects: http://www.codeproject.com/Tips/1064901/A-light-weight-thread-safe-properties-class-for-Cp.

This topic is closed to new replies.

Advertisement