Ifstream object question

I'm loading different files, is it possible/ideal to simply close the file, then open another, or is it necessary to declare a entirely new ifstream object?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "Map_Segment.h"
#include "Map_Tile.h"
#include <iostream>
#include <stdlib.h>
#include <fstream>
using std::ifstream;
using std::string;
using std::cout;
using std::cin;
using std::endl;

Map_Segment::Map_Segment(string roomName)
{
    const string Map_Data = "Map_Data/";
    string document = Map_Data + roomName + "/Valid_Exits.txt";
    ifstream load;
    load.open( document.c_str() );

    if ( load.fail() )
    {
        cout << "Can't find " << document << endl;
        exit(-1);
    }
    else
    {
        bool north, east, south, west;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                load >> north >> east >> south >> west;
                game_board[x][y].set_exits(north, east, south, west);
            }
        }
    }
}
/*new file to load from here*/
If you want to, you can use the .close() and .open() member functions and re-use the same stream object. However, I prefer not to do this and instead utilize RAII.
I'm unfamiliar with RAII, what is that?
https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

An object should be ready to use immediately after construction (e.g. no need to call an initialize or open function) and when you are done with it you let it fall out of scope (e.g. no need to call a destroy/close function).

Example for two files:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(std::ifstream in ("a.txt"))
{
    //...
}
else
{
    std::cerr << "Could not open a.txt" << std::endl;
}
if(std::ifstream in ("b.txt"))
{
    //...
}
else
{
    std::cerr << "Could not open b.txt" << std::endl;
}
> I'm unfamiliar with RAII, what is that?

http://en.cppreference.com/w/cpp/language/raii

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Map_Segment::Map_Segment(string roomName)
{
    // const string Map_Data = "Map_Data/";
    static const string Map_Data_Prefix = "Map_Data/";
    static const string Map_Data_Suffix = "/Valid_Exits.txt";

    // string document = Map_Data + roomName + "/Valid_Exits.txt";
    const string document = Map_Data_Prefix + roomName + Map_Data_Suffix ;

    // ifstream load;
    // load.open( document.c_str() );
    ifstream load( document.c_str() ); // favour single-step initialisation

    if ( load.fail() ) // this is fine: see Cubbi's post
    // if( !load.is_open() ) // fail() may not be set till an attempted input fails
    {
        // cout << "Can't find " << document << endl;
        // favour reporting errors to stderr https://en.wikipedia.org/wiki/Stderr
        std::cerr << "Can't find " << document << '\n' ;

        // exit(-1);
        /*
           Destructors of objects with automatic storage duration (in the chain of callers)
           are not called when we exit with std::exit(). Favour transferring control to main(),
           by throwing an exception, from where a normal error exit can be made with return EXIT_FAILURE
           http://en.cppreference.com/w/cpp/utility/program/exit
        */
        throw std::ifstream::failure( "file could not be opened" ) ;

        // alernatively, we could have just set the exception mask for the input stream
        // http://en.cppreference.com/w/cpp/io/basic_ios/exceptions
    }
    else
    {
        bool north, east, south, west;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                // load >> north >> east >> south >> west;
                // game_board[x][y].set_exits(north, east, south, west);

                if( load >> north >> east >> south >> west ) // attempted input may fail
                  game_board[x][y].set_exits(north, east, south, west);

                else throw std::ifstream::failure( "failed to read data from file" ) ;
            }
        }
    }
}
Last edited on
is_open() ) // fail() may not be set till an attempted input fails

The fstream constructor sets the failbit: http://en.cppreference.com/w/cpp/io/basic_fstream/basic_fstream and 27.9.1.7[ifstream.cons]/2
Thanks, Cubbi.
Kinda like this? Also, I'm unsure how to utilize the attempted input in the if statement for a getline statement, if that is even possible. Also, my code seems... bulky. Is this verbose or is it acceptable?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include "Map_Segment.h"
#include "Map_Tile.h"
#include <iostream>
#include <stdlib.h>
#include <fstream>
using std::ifstream;
using std::string;
using std::cout;
using std::cin;
using std::endl;
using std::cerr;

Map_Segment::Map_Segment(string roomName)
{
    const string Map_Data = "Map_Data/";
    string document = Map_Data + roomName + "/Valid_Exits.txt";
    ifstream load( document.c_str() );

    if ( load.fail() )
    {
        cerr << "Can't find " << document << endl;
        throw ifstream::failure( "File could not be opened." );
    }
    else
    {
        bool north, east, south, west;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                if ( load >> north >> east >> south >> west )
                {
                    game_board[x][y].set_exits(north, east, south, west);
                }
                else
                {
                    throw ifstream::failure( "Failed to read data from file." );
                }
            }
        }
        load.close();
        document.clear();
    }

    document = Map_Data + roomName + "/Exit_Descriptions.txt";
    load.open( document.c_str() );

    if ( load.fail() )
    {
        cerr << "Can't find " << document << endl;
        throw ifstream::failure( "File could not be opened." );
    }
    else
    {
        string north, east, south, west;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                getline( load, north );
                getline( load, east );
                getline( load, south );
                getline( load, west );
                game_board[x][y].set_exit_descriptions( north, east, south, west );
            }
        }
        load.close();
        document.clear();
    }

    document = Map_Data + roomName + "/Area_Descriptions.txt";
    load.open( document.c_str() );

    if ( load.fail() )
    {
        cerr << "Can't find " << document << endl;
        throw ifstream::failure( "File could not be opened." );
    }
    else
    {
        string areaDescription;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                getline( load, areaDescription );
                game_board[x][y].set_area_description( areaDescription );
            }
        }
        load.close();
    }
}
Last edited on
> I'm unsure how to utilize the attempted input in the if statement for a getline statement

Like stm >> str, std::getline( stm, str ) too results in a reference to the stream.
1
2
if( std::getline( stm, str ) ) { /* success */ }
else { /* failed */ }



> Also, my code seems... bulky

It can be split into three (private) functions.
1
2
3
4
5
6
Map_Segment::Map_Segment(string roomName)
{
     get_valid_exits( roomName ) ;
     get_exit_descriptions( roomName ) ;
     get_area_descriptions( roomName ) ;
}


Alternatively, have just one file for a room, which contains all the information: exits, exit descriptions and area descriptions.
What if there are 3-4 getlines? Also, thanks so much for your assistance!
bool_a && bool_b is true only if both bool_a and bool_b are true.
Evaluation of bool_a is sequenced-before evaluation of bool_b.
(and bool_b is evaluated only if bool_a is true)

1
2
3
4
5
if( std::getline( stm, str_a ) &&  std::getline( stm, str_b )  ) 
{ 
     // successful getlines of first str_a and then str_b
     // ...  
}
How does this look?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include "Map_Segment.h"
#include "Map_Tile.h"
#include <iostream>
#include <stdlib.h>
#include <fstream>
using std::ifstream;
using std::string;
using std::cout;
using std::cin;
using std::endl;
using std::cerr;

Map_Segment::Map_Segment(const string roomName)
{
    get_valid_exits(roomName);
    get_area_description(roomName);
}

void Map_Segment::get_valid_exits(const string& areaName)
{
    static const string Map_Data = "Map_Data/";
    static const string Valid_Exits = "/Valid_Exits.txt";
    static const string DocumentName = Map_Data + areaName + Valid_Exits;
    ifstream document(DocumentName.c_str() );
    if ( !document.is_open() )
    {
        cerr << "Can't find " << DocumentName << endl;
        throw ifstream::failure( "File could not be opened." );
    }
    else
    {
        bool north, east, south, west;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                if ( document >> north >> east >> south >> west )
                {
                    game_board[x][y].set_exits( north, east, south, west );
                }
                else
                {
                    throw ifstream::failure( "Failed to read data from file." );
                }
            }
        }
    }
    document.close();
}
void Map_Segment::get_exit_descriptions(const string& areaName)
{
    static const string Map_Data = "Map_Data/";
    static const string Exit_Description = "/Exit_Descriptions.txt";
    static const string DocumentName = Map_Data + areaName + Exit_Description;
    ifstream document( DocumentName.c_str() );
    if ( !document.is_open() )
    {
        cerr << "Can't find " << DocumentName << endl;
        throw ifstream::failure( "File could not be opened." );
    }
    else
    {
        string north, east, south, west;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                if ( getline(document,north) && getline(document,east) && getline(document,south) && getline(document,west) )
                {
                    game_board[x][y].set_exit_descriptions( north, east, south, west );
                }
                else
                {
                    throw ifstream::failure( "Failed to read data from file." );
                }
            }
        }
    }
}
void Map_Segment::get_area_description(const string& areaName)
{
    static const string  Map_Data = "Map_Data/";
    static const string Area_Descriptions = "/Area_Descriptions.txt";
    static const string DocumentName = Map_Data + areaName + Area_Descriptions;
    ifstream document( DocumentName.c_str() );
    if ( !document.is_open() )
    {
        cerr << "Can't find " << DocumentName << endl;
        throw ifstream::failure( "File could not be opened." );
    }
    else
    {
        string areaNameDescription;
        for ( int x = 0; x < MAP_SIZE; x++ )
        {
            for ( int y = 0; y < MAP_SIZE; y++ )
            {
                if ( getline(document, areaNameDescription) )
                {
                    game_board[x][y].set_area_description( areaNameDescription );
                }
                else
                {
                    throw ifstream::failure( "Failed to read data from file." );
                }
            }
        }
    }
    document.close();
}
> How does this look?

Looks good.

Why should there be a const qualifier for a parameter passed by value?

Instead of: Map_Segment::Map_Segment(const string roomName);

Consider Either: Map_Segment::Map_Segment( string roomName ); // pass by value

Or: Map_Segment::Map_Segment( const string& roomName ); // pass by reference to const
Topic archived. No new replies allowed.