3

Hello and sorry if the answer is clear to those out there. I am still fairly new to programming and ask for some guidance.

This function should write just one of the three string parameters it takes in to the txt file I have already generated. When I run the program the function seems to work fine and the cout statement shows the info is in the string and does get passes successfully. The issue is after running the program I go to check the txt file and find it is still blank.

I am using C++17 on visual studio professional 2015.

void AddNewMagicItem(const std::string & ItemKey,
                     const std::string & ItemDescription,
                     const std::string &filename)
{
    const char* ItemKeyName = ItemKey.c_str();
    const char* ItemDescriptionBody = ItemDescription.c_str();
    const char* FileToAddItemTo = filename.c_str();
    std::ofstream AddingItem(FileToAddItemTo);
    std::ifstream FileCheck(FileToAddItemTo);
    AddingItem.open(FileToAddItemTo, std::ios::out | std::ios::app);
    if (_access(FileToAddItemTo, 0) == 0)
    {
        if (FileCheck.is_open())
        {
            AddingItem << ItemKey;
            std::cout << ItemKey << std::endl;
        }
    }
    AddingItem.close(); // not sure these are necessary
    FileCheck.close(); //not sure these are necessary
}

This should print out a message onto a .txt file when you pass a string into the ItemKey parameter.

Thank you very much for your help and again please forgive me as I am also new to stackoverflow and might have made some mistakes in formatting this question or not being clear enough.

ADD ON: Thank you everyone who has answered this question and for all your help. I appreciate the help and would like to personally thank you all for your help, comments, and input on this topic. May your code compile every time and may your code reviews always be commented.

  • 2
    The stream constructors will happily take an `std::string`, no need to give them C-style strings. There's also no need to explicitly close the files, the destructors will do that for you. – BessieTheCookie Jun 27 '19 at 05:16
  • 6
    `std::ofstream AddingItem(FileToAddItemTo);` opened the file. Opening it again with `AddingItem.open(FileToAddItemTo, std::ios::out | std::ios::app);` caused the stream to fail. Solution: Move the open modes into the constructor (`std::ofstream AddingItem(FileToAddItemTo, std::ios::app);`) and eliminate the manual open. Note that only `app` is needed. that the file is `out` is implied by `ofstream`. Looking for a good duplicate. for more details. – user4581301 Jun 27 '19 at 05:16
  • You can't reliably open the file twice at once – M.M Jun 27 '19 at 05:18
  • 3
    You're trying too hard. As well as opening the file twice (the cause of your error) you explicitly close the file which (as your comments suggest) is unnecessary. Also the call to `_access` is unnecessary, if you don't have permissions to access the file then either the file open or the file write are going to fail, that's what you should be checking. – john Jun 27 '19 at 05:28
  • I can't find a decent duplicate for this, but I'll be wreck tomorrow if I stay up any later to write an answer. – user4581301 Jun 27 '19 at 05:45
  • @user4581301 That's an _answer_ please – Lightness Races in Orbit Jun 27 '19 at 10:57
  • Agreed, but by the time I'd gone through all of the potential duplicates I could find and found them wanting any answer I wrote would have been the muddled garbage of a mind in serious need of sleep. – user4581301 Jun 27 '19 at 18:39

3 Answers3

4

As mentioned by previous commenters/answerers, your code can be simplified by letting the destructor of the ofstream object close the file for you, and by refraining from using the c_str() conversion function. This code seems to do what you wanted, on GCC v8 at least:

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


void AddNewMagicItem(const std::string&  ItemKey,
                     const std::string&  ItemDescription,
                     const std::string&  fileName)
{
    std::ofstream  AddingItem{fileName, std::ios::app};

    if (AddingItem) {  // if file successfully opened
        AddingItem << ItemKey;
        std::cout  << ItemKey << std::endl;
    }
    else {
        std::cerr << "Could not open file " << fileName << std::endl;
    }
    // implicit close of AddingItem file handle here
}


int main(int argc, char* argv[])
{
    std::string  outputFileName{"foobar.txt"};
    std::string  desc{"Description"};

    // use implicit conversion of "key*" C strings to std::string objects:
    AddNewMagicItem("key1", desc, outputFileName);
    AddNewMagicItem("key2", desc, outputFileName);
    AddNewMagicItem("key3", desc, outputFileName);

    return 0;
}
jpmarinier
  • 2,625
  • 1
  • 7
  • 16
  • Thanks for the help, I am new to this using files with ofstream and so I appreciate the help with this. I normally have only worked with programs that don't need to store data so this is a first. Thanks everyone for the help and solutions. – RookieProgrammer Jun 28 '19 at 04:32
  • thats also a thing I was unsure of if it would implicitly close the file handler or if I needed to manually make a close the file. – RookieProgrammer Jun 28 '19 at 23:39
1

Main Problem

std::ofstream AddingItem(FileToAddItemTo); 

opened the file. Opening it again with

AddingItem.open(FileToAddItemTo, std::ios::out | std::ios::app); 

caused the stream to fail.

Solution

Move the open modes into the constructor (std::ofstream AddingItem(FileToAddItemTo, std::ios::app);) and remove the manual open.

Note that only the app open mode is needed. ofstream implies the out mode is already set.

Note: If the user does not have access to the file, the file cannot be opened. There is no need to test for this separately. I find testing for an open file followed by a call to perror or a similar target-specific call to provide details on the cause of the failure to be a useful feature.

Note that there are several different states the stream could be in and is_open is sort of off to the side. You want to check all of them to make sure an IO transaction succeeded. In this case the file is open, so if is_open is all you check, you miss the failbit. A common related bug when reading is only testing for EOF and winding up in a loop of failed reads that will never reach the end of the file (or reading past the end of the file by checking too soon).

AddingItem << ItemKey;

becomes

if (!(AddingItem << ItemKey))
{
    //handle failure
}

Sometimes you will need better granularity to determine exactly what happened in order to properly handle the error. Check the state bits and possibly perror and target-specific diagnostics as above.

Side Problem

Opening a file for simultaneous read and write with multiple fstreams is not recommended. The different streams will provide different buffered views of the same file resulting in instability.

Attempting to read and write the same file through a single ostream can be done, but it is exceptionally difficult to get right. The standard rule of thumb is read the file into memory and close the file, edit the memory, and the open the file, write the memory, close the file. Keep the in-memory copy of the file if possible so that you do not have to reread the file.

If you need to be certain a file was written correctly, write the file and then read it back, parse it, and verify that the information is correct. While verifying, do not allow the file to be written again. Don't try to multithread this.

Details

Here's a little example to show what went wrong and where.

#include <iostream>
#include <fstream>

int main()
{
    std::ofstream AddingItem("test");
    if (AddingItem.is_open()) // test file is open
    {
        std::cout << "open";
    }
    if (AddingItem) // test stream is writable
    {
        std::cout << " and writable\n";
    }
    else
    {
        std::cout << " and NOT writable\n";
    }
    AddingItem.open("test", std::ios::app);
    if (AddingItem.is_open())
    {
        std::cout << "open";
    }
    if (AddingItem)
    {
        std::cout << " and writable\n";
    }
    else
    {
        std::cout << " and NOT writable\n";
    }
}

Assuming the working directory is valid and the user has permissions to write to test, we will see that the program output is

open and writable
open and NOT writable

This shows that

std::ofstream AddingItem("test");

opened the file and that

AddingItem.open("test", std::ios::app);

left the file open, but put the stream in a non-writable error state to force you to deal with the potential logic error of trying to have two files open in the same stream at the same time. Basically it's saying, "I'm sorry Dave, I'm afraid I can't do that." without Undefined Behaviour or the full Hal 9000 bloodbath.

Unfortunately to get this message, you have to look at the correct error bits. In this case I looked at all of them with if (AddingItem).

Community
  • 1
  • 1
user4581301
  • 29,019
  • 5
  • 26
  • 45
0

As a complement of the already given question comments:

If you want to write data into a file, I do not understand why you have used a std::ifstream. Only std::ofstream is needed.

You can write data into a file this way:

const std::string file_path("../tmp_test/file_test.txt"); // path to the file
std::string content_to_write("Something\n"); // content to be written in the file

std::ofstream file_s(file_path, std::ios::app); // construct and open the ostream in appending mode

if(file_s) // if the stream is successfully open
{
    file_s << content_to_write; // write data
    file_s.close(); // close the file (or you can also let the file_s destructor do it for you at the end of the block)
}
else
    std::cout << "Fail to open: " << file_path << std::endl; // write an error message

As you said being quite new to programming, I have explicitly commented each line to make it more understandable.

I hope it helps.


EDIT:

For more explanation, you tried to open the file 3 times (twice in writing mode and once in reading mode). This is the cause of your problems. You only need to open the file once in writing mode.

Morever, checking that the input stream is open will not tell you if the output stream is open too. Keep in mind that you open a file stream. If you want to check if it is properly open, you have to check it over the related object, not over another one.

Fareanor
  • 3,773
  • 1
  • 4
  • 26