I am trying to work with ifstream and istringstream using only on variable. I know that both of them are children of istream. So, I am trying to make only one variable of type istream and the intialize depending on some input.
The real problem is that I asked the user to input file path or content of file. Then, I will read it line by line. I tried to do like this.
istream * stream;
if(isFile){
ifstream a("fileOrContent");
stream = &a;
} else {
istringstream a("fileOrContent");
stream = &a;
}
getline(stream,line)
// do something with line
I also tried this
ifstream stream;
if(isFile){
ifstream stream("fileOrContent");
} else {
istringstream stream("fileOrContent");
}
getline(stream,line)
// do something with line
Currently, I using two full copies of my code for each one. Any suggestions of how I might do it?
Thank you
How about refactoring your code like this:
void process(std::istream & is)
{
// ....
}
int main()
{
if (isFile)
{
std::ifstream is("foo.txt");
process(is);
}
else
{
std::istringstream is(str);
process(is);
}
}
What you are trying to do is something like this:
istream * stream;
if(isFile){
stream = new ifstream("fileOrContent");
} else {
stream = new istringstream("fileOrContent");
}
getline(*stream,line)
That said you should use a smart pointer to hold the istream pointer to avoid memory leaks, as pointed out by #πάντα ῥεῖ.
i just did something similar to this. you are almost there
if(isFile) {
stream = new ifstream("whatever");
} else {
stream = new istringstream("whatever");
}
getline(*stream, line);
make sure to delete it though
If you don't want to manage the memory yourself, you can use an unique_ptr which will automatically free the memory when it goes out of scope:
#include <memory>
std::unique_ptr<std::istream> stream;
if(isFile){
stream = std::unique_ptr<std::istream>(new ifstream("fileOrContent"));
} else {
stream = std::unique_ptr<std::istream>(new istringstream("fileOrContent"));
}
getline(*stream,line)
Put the getline and the "do something with line" in a function which takes a std::istream & argument.
Then create either an ifstream or an istringstream, and place the function call into the if/else branches.
void DoSomethingWithLine(std::istream &stream)
{
getline(stream,line);
// do something with line
}
if (isFile){
ifstream a("fileOrContent");
DoSomethingWithLine(a);
} else {
istringstream a("fileOrContent");
DoSomethingWithLine(a);
}
It won't get much simpler than this.
Related
I'm writing a command-line utility for some text processing. I need a helper function (or two) that does the following:
If the filename is -, return standard input/output;
Otherwise, create and open a file, check for error, and return it.
And here comes my question: what is the best practice to design/implement such a function? What should it look like?
I first considered the old-school FILE*:
FILE *open_for_read(const char *filename)
{
if (strcmp(filename, "-") == 0)
{
return stdin;
}
else
{
auto fp = fopen(filename, "r");
if (fp == NULL)
{
throw runtime_error(filename);
}
return fp;
}
}
It works, and it's safe to fclose(stdin) later on (in case one doesn't forget to), but then I would lose access to the stream methods such as std::getline.
So I figure, the modern C++ way would be to use smart pointers with streams. At first, I tried
unique_ptr<istream> open_for_read(const string& filename);
This works for ifstream but not for cin, because you can't delete cin. So I have to supply a custom deleter (that does nothing) for the cin case. But suddenly, it fails to compile, because apparently, when supplied a custom deleter, the unique_ptr becomes a different type.
Eventually, after many tweaks and searches on StackOverflow, this is the best I can come up with:
unique_ptr<istream, void (*)(istream *)> open_for_read(const string &filename)
{
if (filename == "-")
{
return {static_cast<istream *>(&cin), [](istream *) {}};
}
else
{
unique_ptr<istream, void (*)(istream *)> pifs{new ifstream(filename), [](istream *is)
{
delete static_cast<ifstream *>(is);
}};
if (!pifs->good())
{
throw runtime_error(filename);
}
return pifs;
}
}
It is type-safe and memory-safe (or at least I believe so; do correct me if I'm wrong), but this looks kind of ugly and boilerplate, and above all, it is such a headache to just get it to compile.
Am I doing it wrong and missing something here? There's gotta be a better way.
I would probably make it into
std::istream& open_for_read(std::ifstream& ifs, const std::string& filename) {
return filename == "-" ? std::cin : (ifs.open(filename), ifs);
}
and then supply an ifstream to the function.
std::ifstream ifs;
auto& is = open_for_read(ifs, the_filename);
// now use `is` everywhere:
if(!is) { /* error */ }
while(std::getline(is, line)) {
// ...
}
ifs will, if it was opened, be closed when it goes out of scope as usual.
A throwing version might look like this:
std::istream& open_for_read(std::ifstream& ifs, const std::string& filename) {
if(filename == "-") return std::cin;
ifs.open(filename);
if(!ifs) throw std::runtime_error(filename + ": " + std::strerror(errno));
return ifs;
}
As an alternative to Ted's answer (which I think I prefer, actually), you could make your custom deleter a bit smarter:
auto stream_deleter = [] (std::istream *stream) { if (stream != &std::cin) delete stream; };
using stream_ptr = std::unique_ptr <std::istream, decltype (stream_deleter)>;
stream_ptr open_for_read (const std::string& filename)
{
if (filename == "-")
return stream_ptr (&std::cin, stream_deleter);
auto sp = stream_ptr (new std::ifstream (filename), stream_deleter);
if (!sp->good ())
throw std::runtime_error (filename);
return sp;
}
Then the same deleter works for both cases and there are no typing problems.
Live demo
Something which I've used in the past was calling rdbuf to change the buffer of std::cin. That may be useful if you don't want to change existing code using std::cin. You have to pay attention not to use the buffer after it has been destroyed, but, that's nothing that a RAII wrapper can't solve. Something like (not tested, not even proven correct):
struct stream_redirector {
stream_redirector(std::iostream& s, std::string const& filename,
std::ios_base::openmode mode = ios_base::in)
: redirected_stream_{s}
{
if (filename != "-") {
stream_.open(filename, mode);
if (stream_) {
throw std::runtime_error(filename + ": " + std::strerror(errno));
saved_buf_ = redirected_stream_.rdbuf();
redirected_stream_.rdbuf(stream_.rdbuf());
}
}
~stream_redirector() {
if (saved_buf_ != nullptr) {
redirected_stream_.rdbuf(saved_buf_);
}
}
private:
std::stream& redirected_stream_;
std::streambuf* saved_buf_{nullptr};
std::fstream stream_;
};
To be used:
...
stream_redirector cin_redirector(std::cin, filename);
std::string str;
std::cin >> str;
...
I've a task to copy elements from .txt file[direct access file] to .bin file[fixed length record file] (homework).
.txt file holds strings. Every line has one word.
I came up with code below, but I'm not sure if that's what is needed and even slighly correct. Any help will be useful! (I'm new to C++)
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
const int buffer_size = 30;
class Word{
char name[buffer_size];
public:
void setName () // Trying to get every word from a line
{
string STRING;
ifstream infile;
infile.open ("text.txt");
while(!infile.eof()) // To get you all the lines.
{
getline(infile,STRING); // Saves the line in STRING.
}
infile.close();
}
};
void write_record()
{
ofstream outFile;
outFile.open("binFILE.bin", ios::binary | ios::app);
Word obj;
obj.setName();
outFile.write((char*)&obj, sizeof(obj));
outFile.close();
}
int main()
{
write_record();
return 0;
}
NEW APPROACH:
class Word
{
char name[buffer_size];
public:
Word(string = "");
void setName ( string );
string getName() const;
};
void readWriteToFile(){
// Read .txt file content and write into .bin file
string temp;
Word object;
ofstream outFile("out.dat", ios::binary);
fstream fin ("text.txt", ios::in);
getline(fin, temp);
while(fin)
{
object.setName(temp);
outFile.write( reinterpret_cast< const char* >( &object ),sizeof(Word) );
getline(fin, temp);
}
fin.close();
outFile.close();
}
int main()
{
readWriteToFile();
return 0;
}
Word::Word(string nameValue)
{
setName(nameValue);
}
void Word::setName( string nameString )
{
// Max 30 char copy
const char *nameValue = nameString.data();
int len = strlen(nameValue);
len = ( len < 31 ? len : 30);
strncpy(name, nameValue, len);
name[len] = '\0';
}
string Word::getName() const
{
return name;
}
Quick commentary and walk through
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
Avoid using namespace std; while you are learning. It can lead to some really nasty, hard to pin-down bugs as your functions may be silently replaced by functions with the same name in the standard library.
const int buffer_size = 30;
class Word
{
char name[buffer_size];
Since it looks like you are allowed to use std::string why not use it here?
public:
void setName() // Trying to get every word from a line
Really bad name for a function that apparently is supposed to // Trying to get every word from a line
{
string STRING;
ifstream infile;
infile.open("text.txt");
while (!infile.eof()) // To get you all the lines.
{
getline(infile, STRING); // Saves the line in STRING.
}
Few things wrong here. One is the epic Why is iostream::eof inside a loop condition considered wrong?
Next is while the code reads each line, it doesn't do anything with the line. STRING is never stored anywhere.
Finally in a class that sounds as though it should contain and manage a single word, it reads all the words in the file. There may be a case for turning this function into a static factory that churns out a std::vector of Words.
infile.close();
}
};
void write_record()
{
ofstream outFile;
outFile.open("binFILE.bin", ios::binary | ios::app);
ios::app will add onto an existing file. This doesn't sound like what was described in the assignment description.
Word obj;
obj.setName();
We've already coverred the failings of the Word class.
outFile.write((char*) &obj, sizeof(obj));
Squirting an object into a stream without defining a data protocol or using any serialization is dangerous. It makes the file non-portable. You will find that some classes, vector and string prominent among these, do not contain their data. Writing a string to a file may get you nothing more than a count and an address that is almost certainly not valid when the file is loaded.
In this case all the object contains is an array of characters and that should write to file cleanly, but it will always write exactly 30 bytes and that may not be what you want.
outFile.close();
}
int main()
{
write_record();
return 0;
}
Since this is homework I'm not writing this sucker for you, but here are a few suggestions:
Read file line by line will get you started on the file reader. Your case is simpler because there is only one word on each line. Your teacher may throw a curveball and add more stuff onto a line, so you may want to test for that.
Read the words from the file into a std::vector. vector will make your job so easy that you might have time for other homework.
A very simplistic implementation is:
std::vector<std::string> words;
while (getline(infile, STRING)) // To get you all the lines.
{
words.push_back(STRING);
}
For writing the file back out in binary, I suggest going Pascal style. First write the length of the string in binary. Use a known, fixed width unsigned integer (no such thing as a negative string) and watch out for endian. Once the length is written, write only the number of characters you need to write.
Ignoring endian, you should have something like this:
uint32_t length = word.length(); // length will always be 32 bits
out.write((char*)&length, sizeof(length));
out.write(word.c_str(), length);
When you are done writing the writer, write a reader function so that you can test that the writer works correctly. Always test your code, and I recommend not writing anything until you know how you'll test it. Very often coming at a program from the test side first will find problems before they even have a chance to start.
I have three string file that will be stored into dynamic array, but I just try one of three file to test if this succed, so I'll do the same way to handle the three file i have.
the goal i'll shown the string that I get from the file to a ListView
this my code.
void __fastcall TFrmNewPeta::showDefaultRute() {
std::string lineDataAwal;
std::ifstream ifs_Awal;
int tempIndexAwal = 0;
ifs_Awal.open("DefaultDataAwal");
/*counting the line*/
while(std::getline(ifs_Awal,lineDataAwal)){++tempIndexAwal;}
/*use dynamic array to stored string*/
std::string *s = new std::string[tempIndexAwal];
for(int dx=0;dx<tempIndexAwal;dx++)
{
while(std::getline(ifs_Awal,lineDataAwal))
s[dx] = lineDataAwal[dx++];
}
for(int dex =0;dex<tempIndexAwal;++dex)
{
ItemDefult = ListView1->Items->Add();
ItemDefult->Caption = String(IntToStr(dex + 1));
ItemDefult->SubItems->Add(s[dex].c_str());
}
ifs_Awal.close();
delete []s;
s = NULL;
}
there's no errors during compile, but the result ListView just showing the number with this code ItemDefult->Caption = String(IntToStr(dex + 1));
can anyone show me how the best way for i do.
You are reading the file, leaving it open, and expecting to read it again. That won't work because the cursor in the file is at the end of the file (so your second while loop does nothing).
A much better approach would be:
std::vector<std::string> lines;
std::string line;
std::ifstream fin("Youfilename");
while (std::getline(fin, line))
{
lines.push_back(line);
}
fin.close();
// add data to your list view
its easier if you use std::vector for dynamic arrays and don't forget to first include the file header with #include<vector>
void __fastcall TFrmNewPeta::showDefaultRute() {
std::string lineDataAwal;
std::ifstream ifs_Awal;
std::vector<std::string> vec;
ifs_Awal.open("DefaultDataAwal");
/*get the string of lineDataAwal */
while(std::getline(ifs_Awal,lineDataAwal))
{ vec.push_back(lineDataAwal);}
for(int dex =0;dex<vec.size();++dex)
{
ItemDefult = ListView1->Items->Add();
ItemDefult->Caption = String(IntToStr(dex + 1));
ItemDefult->SubItems->Add(vec.at(dex).c_str());
}
ifs_Awal.close();
}
Hope this helps
I am making a file reading class. It should, when constructed open the file with the given string and depending on which constructor is called use the second string supplied to skip through the file to the line after the string given.
Here is my code as it stands:
SnakeFileReader::SnakeFileReader(string filePath)
{
fileToRead_.open(filePath.c_str(), ios::in);
}
SnakeFileReader::SnakeFileReader(string filePath, string startString)
{
fileToRead_.open(filePath.c_str(), ios::in);
string toFind;
while (toFind != startString && !fileToRead_.eof())
{
fileToRead_ >> toFind;
}
}
string SnakeFileReader::ReadLine()
{
string fileLine;
if (!fileToRead_.fail() && !fileToRead_.eof())
fileToRead_ >> fileLine;
return fileLine;
}
int SnakeFileReader::ReadInt()
{
string fileLine = "";
if (!fileToRead_.fail() && !fileToRead_.eof())
fileToRead_ >> fileLine;
int ret;
istringstream(fileLine) >> ret;
return ret;
}
SnakeFileReader::~SnakeFileReader()
{
fileToRead_.close();
}
The problem I have is that in the second constructor I get a segmentation fault. I get another segmentation fault in the read line function as soon as I declare a string.
[Edit] Here is the extra code requested. I am making a "Snake Game" as a part of the first year of my degree. I want the game to read and save files rather than hard code variable values. I will finally be using this class a lot to setup a level in the game. However here are a few lines that should demonstrate how i intend to use this class:
//Level.cpp
std::string fileToRead = "resources/files/level1.txt";
SnakeFileReader sfr(fileToRead);
std::string mapFilePath = sfr.ReadLine();
ImageFile(mapFilePath).load(map_layout);
mapWidth_ = sfr.ReadInt();
mapHeight_ = sfr.ReadInt();
level_cell_size_ = sfr.ReadInt();
map_ = new TileData*[mapWidth_];
for (int i = 0; i < mapWidth_; i++)
{
map_[i] = new TileData[mapHeight_];
}
Layout of the file:
resources/images/Map1_Layout.bmp
40
30
20
Class declaration:
#ifndef SNAKE_FILE_READER_HPP
#define SNAKE_FILE_READER_HPP
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
class SnakeFileReader
{
public:
SnakeFileReader(string filePath);
SnakeFileReader(string filePath, string startString);
~SnakeFileReader();
string ReadLine();
int ReadInt();
private:
ifstream fileToRead_;
};
#endif // SNAKE_FILE_READER_HPP
in the ReadLine function, you return a reference to a variable allocated on the functions stack. you are corrupting the stack, crazy things can happen. your compiler should have warned you about that.
I'm not sure about your constructor, but the problem with ReadLine() is that you're trying to return the memory address of an automatic variable, which is destroyed when you exit the function.
The simplest fix would be to remove the '&' on the return value, and just return a string. But if you're determined to return a memory address, try this instead:
string *SnakeFileReader::ReadLine()
{
string *fileLine = new string;
if (!fileToRead_.fail() && !fileToRead_.eof())
fileToRead_ >> *fileLine;
return fileLine;
}
This will dynamically allocate the string and pass back the pointer. The difference is that dynamic variables are not automatically destroyed when you leave their scope. The string will still exist on the heap until you delete it yourself (which you must remember to do when you're done with it).
I am trying to pull characters from a specific column (in this case, 0) of a text file, and load them into a vector. The code seems to work ok, until it reaches the end, when I get a "string subscript out of range" error, and I do not know how to fix this. Does anyone know what I can do? Here is the relevant code.
class DTree
{
private:
fstream newList;
vector<string> classes;
public:
DTree();
~DTree();
void loadAttributes();
};
void DTree::loadAttributes()
{
string line = "";
newList.open("newList.txt");
string attribute = "";
while(newList.good())
{
getline(newList, line);
attribute = line[0];
classes.push_back(attribute);
}
}
Please try 'while(getline(newList, line)'
Refer here
You can also try something like
ifstream ifs("filename",ios::in);
string temp;
getline(ifs,temp)// Called as prime read
while(ifs)
{
//Do the operations
// ....
temp.clear();
getline(ifs,temp);
}
ifs.clear();
ifs.close();
This works for almost all kinds of files.You can replace getline(ifs,temp) by get() function or by >> operator based on your requirements.