ifstream as class member [duplicate] - c++

I have the following parser class that works in Visual C++
class Parser
{
private:
const char* filename;
std::ifstream filestream;
std::vector<std::string> tokens;
unsigned int linect;
public:
Parser(const char* filename);
bool readline();
std::string getstrtoken(unsigned int i) const { return tokens[i]; }
int getinttoken(unsigned int i) const { return atoi(tokens[i].c_str()); }
};
Parser::Parser(const char* filename) :
filename(filename),
linect(0)
{
filestream = ifstream(filename); // OK in VC++, not with GCC?
}
bool Parser::readline()
{
std::string line;
getline(filestream, line);
std::stringstream ss(line);
std::string token;
tokens.clear();
while(getline(ss, token, ' ')){ if(token != "") tokens.push_back(token); }
linect++;
return (filestream != NULL);
}
But when I try to compile it with GCC 4.8.2, I get errors saying that I cannot assign to filestream. From what I read elsewhere on this site, you can do
std::ifstream filestream(filename);
but you cannot do
std::ifstream filestream;
filestream = ifstream(filename);
which is essentially what I need to do if I want to declare filestream as a member of the Parser class and initialize it in the constructor.
I would like to have the file stream kept within the Parser class so that those who use the parser don't need to declare and keep track of it. It seems to me that this should be self-contained in the Parser class since its internal methods (e.g. readline()) are the only ones that use it.
Is there a way to achieve this that works with both platforms?
Thanks.
edit: My fix was to explicitly call the open() method of ifstream. My parser class constructor now looks like:
Parser::Parser(const char* filename) :
filename(filename),
linect(0)
{
filestream.open(filename);
// Do some checking to make sure the file exists, etc.
}

You can't, since std::ifstream has deleted copy constructor and copy assignment. You may get around by doing
filestream.swap(ifstream(filename)).
The fact that it compiles on visual studio is mostly because it gets inlined into either move assignment or move constructor(I'm not so good to tell you which exactly). If you try
std::ifstream myF;
filestream = myF;
it won't compile.
However you may try to do the move I wrote, or you can just call .open(http://en.cppreference.com/w/cpp/io/basic_ifstream/open)

I think a better solution would be for you to:
Construct a ifstream first.
Construct a Parser using the ifstream object.
Change Parser to store a reference to an istream object. This allows you the flexibility of being able parse the contents of a file, stdin, and a string.
class Parser
{
private:
std::istream& str;
std::vector<std::string> tokens;
unsigned int linect;
public:
Parser(std::istream& s) : str(s) ... {}
...
};

std::ifstream don't have a copy constructor, probably one of the many extensions of VC++. Correct code is:
Parser::Parser(const char* filename) :
filename(filename),
linect(0),
filestream(filename)
{
}
Please take note of member variable and parameter filename. Use this-> or change name (recommended, normally prefix is used for member variables _ or m_)

Related

Initializing "const std::string" from "std::istringstream"

I'm trying to parse a file which is in Key<whitespace>Value format. I'm reading the file lines in an std::istringstream object, and I'm extracting a Key string from it. I want to avoid accidentally changing the value of this Key string by making it const.
My best attempt was initializing a temporary VariableKey object, and then making a constant one out of it.
std::ifstream FileStream(FileLocation);
std::string FileLine;
while (std::getline(FileStream, FileLine))
{
std::istringstream iss(FileLine);
std::string VariableKey;
iss >> VariableKey;
const std::string Key(std::move(VariableKey));
// ...
// A very long and complex parsing algorithm
// which uses `Key` in a lot of places.
// ...
}
How do I directly initialize a constant Key string object?
It's arguably better to separate file I/O from processing, and instead of creating a const Key inside the same function - call a line-processing function that takes a const std::string& key parameter.
That said, if you want to continue with your current model, you can simply use:
const std::string& Key = VariableKey;
There's no need to copy or move anything anywhere. Only const std::string members functions will be accessible via Key.
You can avoid the "scratch" variable by extracting the input into a function:
std::string get_string(std::istream& is)
{
std::string s;
is >> s;
return s;
}
// ...
while (std::getline(FileStream, FileLine))
{
std::istringstream iss(FileLine);
const std::string& Key = get_string(iss);
// ...
(Binding the function's result to a const reference extends its lifetime.)

std::getline with char* to strings

I have this code for reading from a file:
MyObject* LoadObject(wstring filePath)
{
ifstream fileReader;
fileReader.open(filePath);
if (fileReader.is_open())
{
string currentLine;
//std::basic_istream &std::getline
while (getline(fileReader, currentLine))
{
//Logic for loading MyObject* here
}
}
}
Now I'd like to be able to read from a char* buffer as well, is there a way I can keep the same reading logic and just changing how I read the lines?
MyObject* LoadObject(char* buffer, ulong length)
{
//Change how I read each line
//Keep the same logic for loading MyObject*
}
Move your work into something that takes a std::istream & to read from:
MyObject *ReadObject(std::istream &is)
{
string currentLine;
while (getline(is, currentLine))
{
//Logic for loading MyObject* here
}
}
Now just make your other functions use this one:
MyObject* LoadObject(wstring filePath)
{
ifstream fileReader;
fileReader.open(filePath);
if (fileReader.is_open())
{
return ReadObject(fileReader);
}
... //return something else
}
MyObject* LoadObject(char* buffer, ulong length)
{
std::string str(buffer, length);
std::istringsteam iss(str);
return ReadObject(iss);
}
Some small notes:
Pass filePath by const reference since you're not changing it and don't need a copy.
Make buffer a const char * if it's a C string, since you don't need to modify it. Better to use one parameter that knows the length instead of relying on the caller to synchronize the two parameters.
Consider returning a MyObject by value unless you really need the pointer.
Consider naming the two overloads of LoadObject differently to convey their intent. For all the caller knows, the C string overload could be a file path as well.
I'm not sure it would perform well, but you can do something like this:
std::istringstream iss(std::string(buffer, length));
while (getline(iss, currentLine))
{ ... }
You should modify your existing method and take actual reading code into separate method:
MyObject* LoadObjectFromStream(std::istream &in)
{
string currentLine;
//std::basic_istream &std::getline
while (getline(fileReader, currentLine))
{
//Logic for loading MyObject* here
}
}
Then use this method in both variants:
MyObject* LoadObject(wstring filePath)
{
ifstream fileReader;
fileReader.open(filePath);
if (fileReader.is_open())
return loadObjectFromStream( fileReader );
}
MyObject* LoadObject(const char *buffer, size_t size )
{
istrstream stream( buffer, size );
return loadObjectFromStream( stream );
}
you may want to make LoadObjectFromStream private etc, but that details of implementation.

How to have a file stream as a class member

I have the following parser class that works in Visual C++
class Parser
{
private:
const char* filename;
std::ifstream filestream;
std::vector<std::string> tokens;
unsigned int linect;
public:
Parser(const char* filename);
bool readline();
std::string getstrtoken(unsigned int i) const { return tokens[i]; }
int getinttoken(unsigned int i) const { return atoi(tokens[i].c_str()); }
};
Parser::Parser(const char* filename) :
filename(filename),
linect(0)
{
filestream = ifstream(filename); // OK in VC++, not with GCC?
}
bool Parser::readline()
{
std::string line;
getline(filestream, line);
std::stringstream ss(line);
std::string token;
tokens.clear();
while(getline(ss, token, ' ')){ if(token != "") tokens.push_back(token); }
linect++;
return (filestream != NULL);
}
But when I try to compile it with GCC 4.8.2, I get errors saying that I cannot assign to filestream. From what I read elsewhere on this site, you can do
std::ifstream filestream(filename);
but you cannot do
std::ifstream filestream;
filestream = ifstream(filename);
which is essentially what I need to do if I want to declare filestream as a member of the Parser class and initialize it in the constructor.
I would like to have the file stream kept within the Parser class so that those who use the parser don't need to declare and keep track of it. It seems to me that this should be self-contained in the Parser class since its internal methods (e.g. readline()) are the only ones that use it.
Is there a way to achieve this that works with both platforms?
Thanks.
edit: My fix was to explicitly call the open() method of ifstream. My parser class constructor now looks like:
Parser::Parser(const char* filename) :
filename(filename),
linect(0)
{
filestream.open(filename);
// Do some checking to make sure the file exists, etc.
}
You can't, since std::ifstream has deleted copy constructor and copy assignment. You may get around by doing
filestream.swap(ifstream(filename)).
The fact that it compiles on visual studio is mostly because it gets inlined into either move assignment or move constructor(I'm not so good to tell you which exactly). If you try
std::ifstream myF;
filestream = myF;
it won't compile.
However you may try to do the move I wrote, or you can just call .open(http://en.cppreference.com/w/cpp/io/basic_ifstream/open)
I think a better solution would be for you to:
Construct a ifstream first.
Construct a Parser using the ifstream object.
Change Parser to store a reference to an istream object. This allows you the flexibility of being able parse the contents of a file, stdin, and a string.
class Parser
{
private:
std::istream& str;
std::vector<std::string> tokens;
unsigned int linect;
public:
Parser(std::istream& s) : str(s) ... {}
...
};
std::ifstream don't have a copy constructor, probably one of the many extensions of VC++. Correct code is:
Parser::Parser(const char* filename) :
filename(filename),
linect(0),
filestream(filename)
{
}
Please take note of member variable and parameter filename. Use this-> or change name (recommended, normally prefix is used for member variables _ or m_)

C++ string member construction

How do I go about using the constructor for a member which is a string? Here is an example (which is wrong I realize)
class Filestring {
public:
string sFile;
Filestring(const string &path)
{
ifstream filestream(path.c_str());
// How can I use the constructor for the member sFile??
// I know this is wrong, but this illustrates what I want to do.
string sFile((istreambuf_iterator<char>(filestream)), istreambuf_iterator<char>());
}
};
So basically I want to be able to use the member sFile's constructor without doing a string copy. Is there a way to achieve this through assignment?
The best you can use is string::assign:
sFile.assign(istreambuf_iterator<char>(filestream), istreambuf_iterator<char>());
But in C++11 there is a move assignment operator for string so doing the following is almost as efficient (there is no copy of character data, the character data are moved):
sFile = string(istreambuf_iterator<char>(filestream), istreambuf_iterator<char>());
Yet another trick when C++11 move assignment is not avaliable is to use std::swap or string::swap. The efficiency would be probably almost identical to the move assignment variant.
string newContent(istreambuf_iterator<char>(filestream), istreambuf_iterator<char>());
std::swap(sFile, newContent); // or sFile.swap(newContent);
One way is to make filestream a member, so it can be referred to in the constructor initialization list:
class Filestring {
private:
ifstream filestream;
public:
string sFile;
Filestring(const string &path) : filestream(path.c_str()), sFile((istreambuf_iterator<char>(filestream)), istreambuf_iterator<char>())
{}
};
For this to work properly, filestream must appear in the class definition before sFile, or else it won't be initialized in time to use it in the sFile constructor. Of course, you're also adding to the overhead of the Filestring class.
A safer technique which also avoids the overhead of string copying is to use string::swap():
class Filestring {
public:
string sFile;
Filestring(const std::string &path)
{
std::ifstream filestream(path.c_str());
sFile.swap(string((istreambuf_iterator<char>(filestream)), istreambuf_iterator<char>()));
}
};
Not really, you're bound to do a copy if you don't initialize members inside the initializer list, which in your case doesn't seem possible.
Note that your code doesn't initialize the member, but creates a new local variable.
The proper way would be
sFile = std::string((istreambuf_iterator<char>(filestream)), istreambuf_iterator<char>());
You can do it.
But its not worth the extra complexity. If you want to avoid the cost of a copy (which a decent compiler will probably do anyway). You can load it into a temporary then use std::swap().
I would do:
class Filestring {
public:
string sFile;
Filestring(const string &path)
{
ifstream filestream(path.c_str());
string data((istreambuf_iterator<char>(filestream)), istreambuf_iterator<char>());
// Just swap the internal data structures:
std::swap(sFile, data);
}
};
Use initialization lists:
Filestring(const string &path):sFile(/*what you're initializing with*/)
{//more
}
sFile.assign(constructor args);

Initializing private istream data member in C++

I'm currently trying to initialise a private istream variable in class.
The class definition looks like:
#define PARSER_H
class parser {
public:
parser();
parser(string predict_table_file_name);
private:
int getMaxRHS(string predict_table_file_name);
int getMaxPairs(string predict_table_file_name);
int getMaxPairsY(string predict_table_file_name);
int getMaxRHSY(string predict_table_file_name);
int getMaxSymbols(string predict_table_file_name);
int getGoalSymbol(string predict_table_file_name);
int getNumberOfTerminalSymbols(string predict_table_file_name);
string getSymbol(int symbolID);
string getToken();
string openFile(string sourceFile);
bool isTerminalSymbol(string token, string symbolArray[], int terminalSymbols);
istream scanFile;
};
#endif
The variable in question is "istream scanFile". The code I'm using to try and initialize it looks like this.
string parser::openFile(string sourceFile) {
filebuf fb;
fb.open(sourceFile.c_str(), ios::in);
parser::scanFile(&fb);
}
The line "parser::scanFile(&fb);" is giving me the trouble. Apparently the compiler thinks I'm trying to call function, which I guess I am, but I just want to call the constructor on parser::scanFile.
I'm new-ish to C++, so any help would be greatly appreciated.
To solve your question you can add the filebuf as a member variable.
class parser
{
// STUFF LIKE BEFORE
filebuf fb;
istream scanFile;
};
parser::parser()
:fb()
,scanFile(&fb)
{}
string parser::openFile(string sourceFile)
{
fb.open(sourceFile.c_str(), ios::in);
}
But you should probably be using an fstream object:
class parser
{
// STUFF LIKE BEFORE
ifstream scanFile;
};
parser::parser()
:scanFile()
{}
string parser::openFile(string sourceFile)
{
scanFile.open(sourceFile.c_str());
}
see: http://www.cplusplus.com/reference/iostream/fstream/
You must do such initialization in the initialization list of the constructor. Why do you want to implement openFile as a separate function?
Also, if scanFile is used for reading from files, why not use ifstream instead?
Are you going to create parsers that are not associated with files?
If not, try this for the constructor:
parser(string sourceFile) : scanFile(sourceFile.c_str()) { ... other constructor stuff ... }
That assumes that scanFile is a ifstream. Using a istream is more complex. Your original code would not have worked, because the filebuf is destroyed when you exit the openFile function.
You can't, because the object is already constructed. But you can
scanFile.rdbuf( &fb );
but that won't do either, because fb will be destructed as soon as you return from the openFile. But you'll have to resolve this problem yourself, because it's your code flow.
You want something like:
parser::scanFile = istream(&fb);
to invoke the constructor.