In my code, I want to identify some properties about the contents of a file, before deciding how to read the file. (That is, I search for a keyword, if found, it's going to be read with foo(std::ifstream&), else with bar(std::ifstream&)).
I implemented the method that searches for the keyword as
bool containsKeyword(std::ifstream& file, const char* keyword)
{
for ( std::string line; std::getline(file, line); )
{
if ( line == keyword )
{
return true;
}
}
return false;
}
This modifies the position of the file stream (either the end, if the keyword isn't found, or the position of the keyword). However I want that the position is reset after the search. This can be done with a ScopeGuard:
class FilePositionScopeGuard
{
private:
std::ifstream& file;
using FilePosition = decltype(std::declval<std::ifstream>().tellg());
FilePosition initial_position;
public:
FilePositionScopeGuard(std::ifstream& file_)
:
file(file_),
initial_position(file.tellg())
{
}
~FilePositionScopeGuard()
{
file.clear();
file.seekg(initial_position);
}
};
Now we add this to the method:
bool containsKeyword(std::ifstream& file, const char* keyword)
{
FilePositionScopeGuard guard(file);
for ( std::string line; std::getline(file, line); )
{
...
That's nice, because with exactly one additional line in the method, we get the behaviour of not modifying the std::ifstream no matter how the method is exited (one of the returns or an exception).
However, the method bool containsKeyword(std::ifstream&, const char*); does not express the constness. How can I adjust my method to express (at the level of the interface) that the method will not alter the current state?
You could change the signature to take a position-guarded file:
bool containsKeyword(const FilePositionScopeGuard &, const char *);
This allows the caller to pass an ifstream per the current signature (constructing a temporary guard for that operation), or to make their own guard and use it for several operations.
You'll need to make the ifstream member publicly accessible.
Do it with the text comment // the method does read from file but resets the read pointer.
Do not expect a user of the API to be a monkey at keyboard. Specifically don't mark ifstream argument as const while casting constancy out inside the method. It does make difference in a multithreaded program.
Related
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?
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.
I am writing a file reader class Filereader. It has iterators that take a reference to the file the filereader object is assigned to (Filereader fr(filename);). Each instance of the iterator Filereader::output_iterator has a members that store the state of the file when Filereader::output_iterator::operator++() was called last:
void load_state()
{
ias.update(); // this method does not exist
file_m.copyfmt(state);
file_m.seekg(read_pointer);
file_m.seekp(write_pointer);
}
void store_state()
{
ias.restore();
state.copyfmt(file_m);
read_pointer = file_m.tellg();
write_pointer = file_m.tellp();
}
std::fstream& file_m;
std::string line;
int line_number;
const std::string& file_name;
std::ios state;
decltype(file_m.tellg()) read_pointer;
decltype(file_m.tellp()) write_pointer;
boost::io::ios_all_saver ias{file_m};
The incrementation operator looks like this:
output_iterator& operator++()
{
load_state();
if (std::getline(file_m, line)) {
line_number++;
} else {
assert_not_bad();
line_number = -1;
}
store_state();
return *this;
}
The problem is that this does not quite work yet. copyfmt does not save the state flags, so when for instance the eof bit is being set, because one iterator reaches the end of the file, the other ierators will stop moving forward when incremented.
So then I tried to bring boost::io::ios_all_saver into this, but I don't understand hwo I can update the field ias of type boost::io::ios_all_saver like I do with state of type std::ios. I don't understand the documentation. Is there a way to update boost::io::ios_all_saver like I suggest in my snippet?
The state savers are designed as RAII containers: they unconditionally free restore the state on destruction.
Your interface is incompatible with that because users might elect /not/ to restore state (store_state).
If you can live with restore being unconditional at destruction, you can fix it by adhering to the same design.
I ran across a class with a member var as a reference (to a std::istream), with operator void *() and bool operator !() that return that reference, and i'm wondering what that would be for. The class is related to reading/parsing text files with config param pairs. I've pulled out from the (much) larger project the basic parts. In qt (MSVC 2015 community tool chain), i had to change the operator void *() to get a compile, but seems ok on the original linux system.
(In my desktop environment i get: "error: C2440: 'return': cannot convert from 'std::istream' to 'void *'", so i replaced with a call to if(m_in.eof()) and return nullptr)
class LR { // (line reader)
public:
LR(const std::string &filename);
.... other stuff
operator void *() const { return &m_in; }
bool operator !() { return !m_in; }
LR & operator>>(std::string &line);
private:
std::istream &m_in; // input stream
std::ifstream m_in_file; // input file (if specified)
};
LR::LR(const std::string &filename, ... other stuff) :
: m_in(m_in_file)
{
// .... other stuff
if(filename.size() > 0)
{
m_in_file.open(filename.c_str());
}
// .... other stuff
}
and the class that uses this:
class CR { // config reader
public:
// .... other stuff
void Load_Variable(const std::string §ion, value, etc...);
private:
LR m_reader;
};
void CR::Load_Variable(const std::string §ion, value, etc.) {
string line;
bool found = false;
while (m_reader >> line)
{
// .... check stuff, etc.
}
}
Debugging in Qt, while (m_reader >> line) calls the operator void *().
My questions:
Why use a member var reference to a std::istream like this?
What's the purpose of returning the address of member var &m_in when it's always valid because it's a member var (or is this not true?)
Would operator *() of m_reader ever return false? I've searched a bit online and not found any similar examples of this kind of use of operators on member var refs. I need to look next at what it does when the file open fails.
Possibly this code originally used heap pointer vars or some other approach for the m_in var and it was changed somewhere along the way to be a normal member var, with the operators then edited to this? I think the history is not easy to get.
Thanks for the help, stackoverflow is awesome.
The istream has a flag that indicates if an error has occurred and it overrides the ! operator for easy access. You'll often see it used like this:
myStream >> line;
if(!myStream)
cout<<"Error reading data"<<endl;
So you are not returning a reference, you are returning a Boolean flag. Think of the ! operator as isNoError() accessor.
Your class is doing the same thing, just passing through the result from the stream it wraps.
The * operator is probably there as backward compatibility measure. Probably the existing code base is expecting a pointer and this was added so that the existing code base works with the new implementation.
I have something like that :
istream ifs("/path/to/my/file.ppm", ios::binary);
So now, for checking the extension file, It's necessary to get the name of the file.
I'm using my own function read :
... readPPM(std::istream& is) {}
It's is possible to get the /path/to/my/file.ppm in a string from the istream& variable ?
You almost certainly actually used
std::ifstream ifs(...);
// ^
However, even so the stream doesn't retain the name used to open it: there is rarely a need to doing so and it would be a wasted resource for most applications. That is, if you need the name later, you'll need to retain it. Also, not all streams have a name. For example, an std::istringstream doesn't have a name.
If you can't pass the stream's name separate from the stream, you can attach the name, e.g., using the pword() member:
int name_index() {
static int rc = std::ios_base::xalloc(); // get an index to be used for the name
return rc;
}
// ...
std::string name("/path/to/my/file.ppm");
std::ifstream ifs(name, ios::binary);
ifs.pword(name_index()) = const_cast<char*>(name.c_str());
// ...
char const* stream_name = static_cast<char*>(ifs.pword(name_index()));
The stream won't maintain the pointer in any shape or form, i.e., with the above setup the name needs to outlive the ifs object. If necessary the objects stored with pword() can be maintained using the various callbacks but doing so is non-trivial.
I have two functions read() and write(). I read a file in the read() function and store a line in the header in a variable. Now i want the write() function to write that same line to a new file. But how can i use the same variable or information from the other function? What is the way to do this?
Here is some info about the code:
After including necessary files, it says this
HX_INIT_CLASS(HxCluster,HxVertexSet);
The name of the class is HxCluster and it would be great if someone can tell me why it is not like we define classes in the simple way: class class_name {};
The I have many functions out of which two are read() and write(). They both take one argument only which is the file to be read and the file to be written to in the respective cases. I don't know if writing the code for that will help here.
If I understood you well, this is just what in C++ the structures/classes/objects are for. For example:
class FileLineWriter
{
public:
FileLineWriter();
void read(istream& inputfile);
void write(ostream& putfile);
private:
string line_of_text;
};
void FileLineWriter::read(istream& s)
{
// s >> this->line_of_text; // possible, but probably will not do what you think
getline(s, this->line_of_text);
}
void FileLineWriter::read(ostream& s)
{
s << this->line_of_text;
}
...
FileLineWriter writer;
writer.read(firstfile);
writer.write(secondfile);
note that the above is NOT a working code. It is just a sample. You will have to fix all typos, missing namespaces, headers, add stream opening/closing/error handling, etc.
You return the variable from read and pass it as a parameter to write. Something like this
std::string read()
{
std::string header = ...
return header;
}
void write(std::string header)
{
...
}
std::string header = read();
write(header);
Passing information between functions is a basic C++ skill to learn.
If I have understood this right then I would suggest that you save the info on the variable to a string or an int depending on what kind of info it is.
I would also recommend to always include some code for us to be able to give you some more help
You can either make write take an argument, void write(std::string text) or you can store the string you read as a global variable std::string text at the top of your .cpp file, text = ... in your read function (replace ... with ifstream or whatever you use) and then write text in your write funcion.
Sure,
Use pointers!
void main(){
char* line = malloc(100*sizeof(char));
read_function (line);
write_function (line);
}
void read_function(char* line){
.... read a line
strcpy (line, the_line_you_read_from_file);
}
void write_function (char* line){
fprintf (fp,"%s", line);
}