Yuriy F
Yuriy F

Reputation: 361

How to read data from a file from within a function

I want to make my code more efficient, specifically the reading of data from a text file. Here is a snapshot of what it looks like now:

values V(name); 
    V.population = read_value(find_line_number(name, find_in_map(pop, mapping)));
    V.net_growth = read_value(find_line_number(name, find_in_map(ngr, mapping)));
... // and so on

Basically, the read_value function creates an ifstream object, opens the file, reads one line of data, and closes the file connection. This happens many times. What I want to do is to open the file once, read every line that is needed into the struct, and then close the file connection.

Here is the creating values struct function with parameters:

static values create_struct(std::string name, std::map<std::string, int> mapping) {
    values V(name); 
    V.population = read_value(find_line_number(name, find_in_map(pop, mapping)), file);
    V.net_growth = read_value(find_line_number(name, find_in_map(ngr, mapping)), file);
    // more values here
    return V;
}

The function that calls create_struct is shown below:

void initialize_data(string name) {
    // read the appropriate data from file into a struct
    value_container = Utility::create_struct(name, this->mapping);
}

I am thinking of instead defining the ifstream object in the function initialize_data. Given what is shown about my program, would that be the best location to create the file object, open the connection, read the values, then close the connection? Also, would I need to pass in the ifstream object into the create_values struct, and if so, by value, reference or pointer?

Upvotes: 0

Views: 125

Answers (1)

James Poag
James Poag

Reputation: 2380

The short answer is to create your ifstream object first and pass it as reference to your parser. Remember to seek the stream back to the beginning before you leave your function, or when you start to read.

The RAII thing to do would be to create a wrapper object that automatically does this when it goes out of scope.

class ifStreamRef{
    ifStreamRef(std::ifstream& _in) : mStream(_in){}
    ~ifStreamRef(){mStream.seekg(0);}
    std::ifstream& mStream;
}

Then you create a wrapper instance when entering a method that will read the fstream.

void read_value(std::ifstream& input, ...){
    ifStreamRef autoRewind(input);
}

Or, since the Ctor can do the conversion...

void read_value(ifStreamRef streamRef, ...) {
    streamRef.mStream.getLine(...);
}

std::ifstream itself follows RAII, so it will close() the stream for you when your stream goes out of scope.


The long answer is that you should read up on dependency injection. Don't create dependencies inside of objects/functions that can be shared. There are lots of videos and documents on dependency injection and dependency inversion.

Basically, construct the objects that your objects depend on and pass them in as parameters.

The injection now relies on the interface of the objects that you pass in. So if you change your ifStreamRef class to act as an interface:

class ifStreamRef{
    ifStreamRef(std::ifstream& _in) : mStream(_in){}
    ~ifStreamRef(){mStream.seekg(0);}

    std::string getLine(){
        // todo : mStream.getLine() + return "" on error;
    } 
    bool eof() { return mStream.eof(); }
    std::ifstream& mStream;
}

Then later on you can change the internal implementation that would take a reference to vector<string>& instead of ifstream...

class ifStreamRef{
    ifStreamRef(std::vector<string>& _in) : mStream(_in), mCursor(0){}
    ~ifStreamRef(){}

    std::string getLine(){
        // todo : mStream[mCursor++] + return "" on error;
    } 
    bool eof() { return mCursor >= mStream.size(); }
    std::vector<string>& mStream;
    size_t mCursor;
}

I have oversimplified a few things.

Upvotes: 1

Related Questions