Capture a functions standard output and write it to a file - c++

What I try to do is to write all output inside a function into a file. Maybe I need a way to assign all output (not only arrays) in test_func to some kind of variable so that I can return it, but I can't figure out.
#include <iostream>
#include <fstream>
#include <functional>
using namespace std;
void test_func()
{
int a[] = {20,42,41,40};
int b[] = {2,4,2,1};
cout << "Below is the result: "<< endl;
for (int i=0; i<4; i++){
cout << "***********************" << endl;
cout << a[i] << " : " << b[i] <<endl;
cout << "-----------------------" << endl;
}
}
void write_to_file(function<void()>test_func)
{
ofstream ofile;
ofile.open("abc.txt");
ofile << test_func(); // This is not allowed
ofile.close();
}
int main()
{
write_to_file(test_func);
return 0;
}
I need to get all output from test_func instead of only the array a and b, because I have multiple functions in different formats, which are all needed to write into the file using same function write_to_file.
Is there any logical way to do this? (or alternative to function?)

Here is some code that will work the way you want. You have to replace std::couts current rdbuf() with the one of the file streams, and reset it afterwards:
void write_to_file(function<void()>test_func) {
ofstream ofile;
ofile.open("abc.txt");
std::streambuf* org = cout.rdbuf(); // Remember std::cout's old state
cout.rdbuf(ofile.rdbuf()); // Bind it to the output file stream
test_func(); // Simply call the anonymous function
cout.rdbuf(org); // Reset std::cout's old state
ofile.close();
}
Here you can see it running as you intended: Demo
To overcome the problem with the varying function signatures, you can use a delegating lambda function:
void test_func2(double a, int b) {
cout << a << " * " << b << " = " << (a * b) << endl;
}
int main() {
// Create a lambda function that calls test_func2 with the appropriate parameters
auto test_func_wrapper = []() {
test_func2(0.356,6);
};
write_to_file(test_func_wrapper); // <<<<< Pass the lambda here
// You can also forward the parameters by capturing them in the lambda definition
double a = 0.564;
int b = 4;
auto test_func_wrapper2 = [a,b]() {
test_func2(a,b);
};
write_to_file(test_func_wrapper2);
return 0;
}
Demo
You can even do this with a little helper class, which generalizes the case for any std::ostream types:
class capture {
public:
capture(std::ostream& out_, std::ostream& captured_) : out(out_), captured(captured_), org_outbuf(captured_.rdbuf()) {
captured.rdbuf(out.rdbuf());
}
~capture() {
captured.rdbuf(org_outbuf);
}
private:
std::ostream& out;
std::ostream& captured;
std::streambuf* org_outbuf;
};
void write_to_file(function<void()>test_func)
{
ofstream ofile;
ofile.open("abc.txt");
{
capture c(ofile,cout); // Will cover the current scope block
test_func();
}
ofile.close();
}
Demo
So regarding your comment:
Sure, but I will require something to store those cout, or maybe there's another completely different way instead of using test_func() for the process?
We have everything at hand now to do this
#include <iostream>
#include <fstream>
#include <functional>
#include <string>
#include <sstream>
using namespace std;
void test_func1(const std::string& saySomething) {
cout << saySomething << endl;
}
void test_func2(double a, int b) {
cout << "a * b = " << (a * b) << endl;
}
class capture {
public:
capture(std::ostream& out_, std::ostream& captured_) : out(out_), captured(captured_), org_outbuf(captured_.rdbuf()) {
captured.rdbuf(out.rdbuf());
}
~capture() {
captured.rdbuf(org_outbuf);
}
private:
std::ostream& out;
std::ostream& captured;
std::streambuf* org_outbuf;
};
int main() {
std::string hello = "Hello World";
auto test_func1_wrapper = [hello]() {
test_func1(hello);
};
double a = 0.356;
int b = 6;
auto test_func2_wrapper = [a,b]() {
test_func2(a,6);
};
std::stringstream test_func1_out;
std::stringstream test_func2_out;
std::string captured_func_out;
{ capture c(test_func1_out,cout);
test_func1_wrapper();
}
{ capture c(test_func2_out,cout);
test_func2_wrapper();
}
captured_func_out = test_func1_out.str();
cout << "test_func1 wrote to cout:" << endl;
cout << captured_func_out << endl;
captured_func_out = test_func2_out.str();
cout << "test_func2 wrote to cout:" << endl;
cout << captured_func_out << endl;
}
And the Demo of course.

The line ofile << test_func(); means that returned value of called test_func(); is directed to that stream. It doesn't do anything to actions done within function called. You may pass stream to the function though.
void test_func(ostream& outs)
{
outs << "Below is the result: "<< endl;
}
and call it with cout or ofile - any ostream as argument.
void write_to_file(function<void(ostream&)>test_func)
{
ofstream ofile;
ofile.open("abc.txt");
test_func(ofile); // This is not allowed
ofile.close();
}
But if the behaviour of function as stream manipulator is something what you want, you have to design a proper operator.
ostream& operator<< (ostream& o, void(*func)(ostream&) )
{
func(o);
return o;
}
Then you can write something like
cout << test_func << " That's all, folks\n";
Note, that test_func isn't called here, its id used as expression results in function's address being passed to operator<<.
Real stream manipulators (e.g. https://en.cppreference.com/w/cpp/io/manip/setw ) implemented not as functions , but as templates of functional objects, the argument of setw in line:
is >> std::setw(6) >> arr;
is actually argument of a constructor

What I try to do is to write all output inside a function into a file.
I often use a std::stringstream to act as a temporary repository for text, i.e. the ss holds and bundles all output into a 'buffer' (a text string) for delay'd output to the file.
For your test_func, you might add a ss reference parameter :
void test_func(std::stringsteam& ss)
{
int a[] = {20,42,41,40};
int b[] = {2,4,2,1};
cout << "Below is the result: "<< endl;
for (int i=0; i<4; i++){
ss << "***********************" << endl;
ss << a[i] << " : " << b[i] <<endl;
ss << "-----------------------" << endl;
}
}
A std::stringstream is essentially a ram-based ofile (with none of the hard disk overhead).
So you can run many test_func's, lump all the output together into one ss, and empty the ss content to the one file.
Or, you might invoke 1 test_func, output / append that ss contents to your ofile, then clear the ss for re-use.
You also might invoke 1 test func, output that ss contents to a unique ofile, then clear the ss and do the next test func, etc.
Note: a) std::stringstream uses one std::string as a working buffer, and b) std::string keeps its data in dynamic memory. I seldom worry about how big the ss gets. But, if you are worried, and have an estimate, you can easily use reserve to set the string size. Knowing this size will allow you to plan to control very big output files.
Next, consider keeping stringstream out of the test_func's, and instead keep it in the outer data gathering function:
void write_to_file(function<void()>test_func)
{
std::stringstream ss; // temporary container
test_func(ss); // add contributions
test_func2(ss); // add contributions
test_func3(ss); // add contributions
// ...
test_funcN(ss); // add contributions
// when all testing is complete, output concatenated result to single file
ofstream ofile;
ofile.open("abc.txt");
ofile << ss.str();
ofile.close();
}
int main()
{
write_to_file(test_func);
return 0;
}
Note: to empty a ss, I use 2 steps:
void ssClr(stringstream& ss) { ss.str(string()); ss.clear(); }
// clear data clear flags
Note: I encapsulate my coding efforts into one or more c++ classes. In my code, the ss objects are declared as a data attribute of my class, and thus accessible to all function attributes of that class, including each test_funci (i.e. no need to pass the ss)

Related

How to use pointer with struct to refer to the field of each struct

Below code is the normal way to get the input from a text and store it in an array in a structure.
Wanted to ask how can i use pointer to store all these data into the array of structure ? Like p1->Years (this is without array, but how can i apply this to way of writing in below code)
Any better suggestion to use pointer to take in the input?
int years = 4;
struct maju_company {
int Year;
float quarter1, quarter2, quarter3, quarter4, total_sales, average_sales;
};
int main() {
string line;
maju_company p1[years];
fstream yeecinnfile("MajuSales.txt");
if(yeecinnfile.is_open()) {
//ignoring the first four line of code and store the rest of the code
string line1,line2,line3,line4;
getline(yeecinnfile,line1);
getline(yeecinnfile,line2);
getline(yeecinnfile,line3);
getline(yeecinnfile,line4);
while(!yeecinnfile.eof()) {
for(int i =0; i<years; i++) {
yeecinnfile>>p1[i].Year>>p1[i].quarter1>>p1[i].quarter2>>p1[i].quarter3>>p1[i].quarter4;
}
}
for(int i =0; i<years; i++) {
cout<<p1[i].Year<<setw(10)<<p1[i].quarter1<<setw(10)<<p1[i].quarter2<<setw(10)<<p1[i].quarter3<<setw(10)<<p1[i].quarter4<<endl;
}
cout<<endl;
}
}
I see nothing wrong with the way you do this.
However, you could create a pointer to each record inside the loop
maju_company* p = &p1[i];
and then use p-> instead of p1[i]., but I really don't see this as an improvement.
If the reading loop looks too complicated, I would rather move the code to a separate function, perhaps
void read_record(maju_company& company);
or maybe
maju_company read_record();
and then only have to handle a single company inside the function (so no indexing and no ponters there).
I think you wouldn't need pointers at all for your example.
Use a std::vector to hold all your data and then there are other
things from C++ I think you should learn to use, example here :
(if you have questions let me know)
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
// dont use : using namespace std;
struct maju_company_data
{
int year;
float quarter1, quarter2, quarter3, quarter4, total_sales, average_sales;
};
// describe how to stream data to an output stream (like std::cout)
std::ostream& operator<<(std::ostream& os, const maju_company_data& data)
{
os << "-----------------------------------------------------\n";
os << "Company data for year : " << data.year << "\n";
os << "Quarter 1 : " << data.quarter1 << "\n";
os << "Quarter 2 : " << data.quarter1 << "\n";
os << "Quarter 3 : " << data.quarter1 << "\n";
os << "Quarter 4 : " << data.quarter1 << "\n";
os << "\n";
return os;
}
int main()
{
// no need to manage pointers yourself use a vector
std::vector<maju_company_data> company_yearly_data; // give variables a meaningful name
std::ifstream ifile("MajuSales.txt"); // ifstream your using file as input
std::string line1, line2, line3, line4;
// ignore first line
ifile >> line1;
while (ifile >> line1 >> line2 >> line3 >> line4) // probably you need to read a few more lines here
{
maju_company_data data;
// convert read strings to numbers
data.year = std::stoi(line1);
data.quarter1 = std::stof(line2);
data.quarter2 = std::stof(line3);
data.quarter3 = std::stof(line4);
//..
//data.quarter4 = std::stof(line5);
//data.total_sales = std::stof(line6);
company_yearly_data.push_back(data);
};
// this is a range based for loop
// it is prefered since you cant go out of bounds
// const auto& means that data will be an unmodifiable
// reference to each of the structs stored in the vector
for (const auto& data : company_yearly_data)
{
std::cout << data; // since we overloaded << this loop will be nice and clean
}
return 0;
}
A C++ approach to this to overload the istream operator>> and ostream operator<< for your specific type. E.g.
#include <algorithm>
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
static constexpr auto years{4};
struct maju_company {
int Year{};
float quarter1{}, quarter2{}, quarter3{}, quarter4{};
float total_sales{}, average_sales{}; // ALWAYS init your floats.
};
auto& operator>>(std::istream& is, maju_company& mc) {
is >> mc.Year
>> mc.quarter1 >> mc.quarter2 >> mc.quarter3 >> mc.quarter4
>> mc.total_sales >> mc.average_sales;
return is;
}
auto& operator<<(std::ostream& os, maju_company const& mc) {
os << mc.Year
<< std::setw(10) << mc.quarter1
<< std::setw(10) << mc.quarter2
<< std::setw(10) << mc.quarter3
<< std::setw(10) << mc.quarter4;
return os;
}
You can then go on to use the type using the std library, e.g.
int main() {
auto p1{std::array<maju_company, years>{}};
{
auto fs{std::fstream("MajuSales.txt")};
if (!fs.is_open()) return -1;
{
// throw away 4 lines
auto dummy{std::string{}};
for (auto i{0}; i < 4; ++i) getline(fs, dummy);
}
std::copy_n(std::istream_iterator<maju_company>{fs},
years,
begin(p1));
}
std::copy(cbegin(p1), cend(p1),
std::ostream_iterator<maju_company>{std::cout, "\n"});
}

Use << operator with own function to "override" cout

I know that there is a lot of topics about overloading << operator, but it seems to always be used in class in order to make it support << operator. Hope I would not be duplicate
What I want to do is (I think) quite different (and probably simpler).
I have a console app with 2 threads, both writing on console. I want to avoid them from smashing to each other in the console, so I use a mutex to prevent a thread from outputing when the other one is, so i've created the function :
void print(string s){
globals::console_mtx.lock();
cout << s << endl;
globals::console_mtx.unlock();
}
I would like it to be usable like this, regardless of the data type :
int i=5;
print << "Some text" << i << endl;
Is << overloading what I need ? what would be the simplest way to achieve it ?
Thanks
Is this what you're thinking about?:
struct print_t {};
static const print_t print = print_t();
print_t& operator<<(print_t& p, const std::string& s) {
globals::console_mtx.lock();
std::cout << s << endl;
globals::console_mtx.unlock();
return p;
}
NB. there are about a dozen ways to make it better, like having print as a function with unique type, etc. to make sure you don't run into static (de)initialization order fiasco (avoid the above in static deinitialization if possible). You might use the definition of cout in std as a cheat sheet.
The problem with just overloading << is that:
print << "Some text" << i << endl;
Actually calls print.operator<<() separately for "Some text", i, and endl (also you need to make sure to have overloads for int and endl). If you lock and unlock for each call, then another thread could get the lock before you are done with all 3 calls. If for instance thread 2 had
print << "Other text" << i+1 << endl;
Then you might end up with:
"Some TextOther Text65\n\n"
The simplest way to deal with it is to output to a stringstream first, and then use a normal print function to print the entire thing protected by a locked mutex:
stringstream ss;
ss <<"Some text" << i << endl;
print(ss.str());
If you insist on using streams directly, then streams handle this kind of thing by only actually writing when it receives an instruction to flush. endl automatically flushes the stream, which is one of the ways it differs from "\n".
You then implement a custom std::stringbuf that locks the mutex and outputs to console on sync(), then you construct an ostream for each thread to use as its personal printing output stream.
Example code:
#include <iostream>
#include <sstream>
#include <mutex>
#include <thread>
#include <string>
// Increase risk of race condition if one can be triggered.
char slow_get_ch(char ch)
{
for (unsigned int i = 0; i < 10000; ++i)
{
for (unsigned int j = 0; j < 10000; ++j)
{
}
}
return ch;
}
class print_buf : public std::stringbuf
{
std::mutex& mtx_;
public:
print_buf(std::mutex& mtx)
:
mtx_(mtx)
{
}
protected:
int sync() final
{
std::unique_lock<std::mutex> lck(mtx_);
std::string val = this->str();
std::cout << val;
this->str("");
return 0;
}
};
void print_worker(std::ostream* print_stream_ptr,char ch)
{
std::ostream& print = *print_stream_ptr;
// Print 5 lines of 20 times ch.
for (unsigned int i = 0; i < 5; ++i) {
for (unsigned int j = 0; j < 20; ++j) {
print << slow_get_ch(ch);
}
print << std::endl;
}
}
int main()
{
std::mutex print_mutex;
print_buf buf1(print_mutex);
print_buf buf2(print_mutex);
print_buf buf3(print_mutex);
std::ostream p1(&buf1);
std::ostream p2(&buf2);
std::ostream p3(&buf3);
std::thread t1(print_worker, &p1, 'a');
std::thread t2(print_worker, &p2, 'b');
std::thread t3(print_worker, &p3, 'c');
t1.join();
t2.join();
t3.join();
return 0;
}

Undeclared Identifier in my function call (C++)

I am a new programmer working in C++, I am trying to make a program that will import information from a file to an output file and then I'm going to do a search algorithm on the data. I am trying to use a structure of data and import that into an array and then call it in the main program.
For some reason I can't, for the life of me, get my function call to work; I keep getting an undeclared identifier error on inputFile in my function call in the main program. I realize I'm probably doing something fundamentally wrong, so I would really appreciate any help that can be given.
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <fstream>
using namespace std;
const int MAX_LOG_SIZE = 7584;
const string LOGFILE ="crimes.dat";
const string OUTPUT_FILE ="crimesorted.log";
// Structure of strings based on info from crimes.dat
struct CrimeInfo
{
string Crimedescr;
string Date;
string Time;
string Address;
string Grid;
string Latitude;
string Longitude;
};
CrimeInfo crimeList [MAX_LOG_SIZE];
void openInputFile(ifstream& inputFile, string inputFilename)
// here we open the input file crimes.dat
{
inputFile.open(inputFilename.c_str());
while (inputFile.fail())
{
cout << "Failed to open input file: " << inputFilename << ".\n";
exit(1);
}
};
void getLogEntry(ifstream &LOGFILE, CrimeInfo &entry)
{
getline(LOGFILE, entry.Date);
getline(LOGFILE, entry.Time);
getline(LOGFILE, entry.Address);
getline(LOGFILE, entry.Grid);
getline(LOGFILE, entry.Crimedescr);
getline(LOGFILE, entry.Latitude);
getline(LOGFILE, entry.Longitude);
}
/* opens an output file */
void openOutputFile(ofstream& outputFile, string outputFilename)
{
outputFile.open(outputFilename.c_str());
if (outputFile.fail())
{
cout << "Failed to open output file: " << outputFilename << ".\n";
exit(2);
}
}
void outputLogFile(string outputFilename, CrimeInfo arr[], int size)
{
// open output files
ofstream outputLogFile;
openOutputFile(outputLogFile, outputFilename);
// output the crime file
outputLogFile << "\nCrime log sort ^^:\n\n";
for (int i = 0; i < size; i++)
{
outputLogFile << arr[i].Date << " ";
outputLogFile << arr[i].Address << " (";
outputLogFile << arr[i].Longitude << " ";
outputLogFile << arr[i].Latitude << " ";
outputLogFile << arr[i].Time << " ";
outputLogFile << arr[i].Grid << " ";
outputLogFile << arr[i].Crimedescr << "";
outputLogFile << endl;
}
outputLogFile.close();
}
int main()
{
outputLogFile(OUTPUT_FILE, crimeList, MAX_LOG_SIZE);
for (int i =0; i < MAX_LOG_SIZE; i++)
getLogEntry(inputFile, crimeList[i].Date);
}
There are a lot of problems with your code. To help you out, I went through your code and left a lot of my own comments to tell you some suggestions I had; to make it easy, I deleted your comments so there's no confusion on what was yours and what I put there.
Here are some things I noticed in your code:
using namespace std is generally considered a very bad practice. Instead, just specify the namespace (e.g. std::string instead of just string).
You declared LOGFILE as a string at the top of your program, but then tried to use it as an ifstream& in the function getLogEntry.
Your main method is out of order. I'm assuming you want to load some data into the program from a file and then output that data to another file. The way you have it in your main method is, first, you output information you don't have yet and, second, import information but don't do anything with it.
You have a LOT of functions. As a general rule of thumb, don't make a whole function for opening a file, then a separate one for using it, then a separate one for closing it. There are a lot of big reasons why not to do this. The biggest reasons are that your program becomes very difficult to follow, and no one else will be able to use your code. In real-world applications, your code is only 20% for the computer and 80% for other programmers.
There are various formatting errors and such.
So, here is your original code with my comments...
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdlib> // Unneeded since other headers here already include this
#include <fstream>
using namespace std; // NEVER globally use the entire standard namespace!
const int MAX_LOG_SIZE = 7584; // Can be declared 'constexpr'
const string LOGFILE ="crimes.dat";
const string OUTPUT_FILE ="crimesorted.log";
/*
NOTE:
> It often looks a lot cleaner to have a header part of your code
and then define your functions seperately. This is good practice
for when you need to start using header files with big projects
*/
struct CrimeInfo
{ // Can declare all variables by only listing type once if they're all the same type
string Crimedescr;
string Date;
string Time;
string Address;
string Grid;
string Latitude;
string Longitude;
};
CrimeInfo crimeList [MAX_LOG_SIZE]; // This should be in 'main()'
/*
This should not be its own function.
Making too many function can make things look a bit confusing.
Here, this is only 4 lines of code, so you shouldn't be making
an entire function for it.
*/
void openInputFile(ifstream& inputFile, string inputFilename)
{
inputFile.open(inputFilename.c_str());
while (inputFile.fail())
{
cout << "Failed to open input file: " << inputFilename << ".\n";
exit(1);
}
};
/*
This should also just be written out where its used. There's
no need to make a whole function for a task like this.
ERROR HERE:
> LOGFILE is NOT an std::ifstream! It is a std::string!
*/
void getLogEntry(ifstream &LOGFILE, CrimeInfo &entry)
{
getline(LOGFILE, entry.Date);
getline(LOGFILE, entry.Time);
getline(LOGFILE, entry.Address);
getline(LOGFILE, entry.Grid);
getline(LOGFILE, entry.Crimedescr);
getline(LOGFILE, entry.Latitude);
getline(LOGFILE, entry.Longitude);
}
/*
This should not be its own function.
Making too many function can make things look a bit confusing.
Here, this is only 4 lines of code, so you shouldn't be making
an entire function for it.
*/
void openOutputFile(ofstream& outputFile, string outputFilename)
{
outputFile.open(outputFilename.c_str());
if (outputFile.fail())
{
cout << "Failed to open output file: " << outputFilename << ".\n";
exit(2);
}
}
// It's a good idea to use some sort of documentation style for functions
void outputLogFile(
// Declare variables const when they aren't modified
/* (const) */ string outputFilename,
/* (const) */ CrimeInfo arr[],
/* (const) */ int size)
{
ofstream outputLogFile;
openOutputFile(outputLogFile, outputFilename); // Just write out the code
outputLogFile << "\nCrime log sort ^^:\n\n";
for (int i = 0; i < size; i++)
{
/*
You only need to declare the name of the stream one time
e.g.
outputLogFile << thing1 << thing2
<< thing3 << thing4 << thing5
<< thing6
<< endl;
*/
outputLogFile << arr[i].Date << " ";
outputLogFile << arr[i].Address << " (";
outputLogFile << arr[i].Longitude << " ";
outputLogFile << arr[i].Latitude << " ";
outputLogFile << arr[i].Time << " ";
outputLogFile << arr[i].Grid << " ";
outputLogFile << arr[i].Crimedescr << ""; // Empty quotes not needed here
outputLogFile << endl;
}
outputLogFile.close();
}
int main()
{
// What data are you outputting?
outputLogFile(OUTPUT_FILE, crimeList, MAX_LOG_SIZE);
// Are you trying to load the data you just outputted?
for (int i =0; i < MAX_LOG_SIZE; i++)
{ // I added these braces, but it's a good idea to always have braces
// You have not declared 'inputFile' anywhere
getLogEntry(inputFile, crimeList[i].Date);
}
}
Instead of leaving you to have to figure all that out on your own (I know how frustrating that can be), I went ahead and wrote your program how I'd do it. I tried to put comments in a lot of places to make it easy to follow along with. If you have any questions about it, feel free to ask me.
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
/*
If you're using C++17, the lines below can just become one line:
using std::cin, std::cout, std::endl, std::ifstream,
std::ofstream, std::string, std::getline;
*/
using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
constexpr int MAX_LOG_SIZE = 7584;
const string LOGFILE_NAME = "crimes.dat";
// I'm assuming: inputFile ^^^
// outputFile vvv
const string OUTPUT_FILE_NAME = "crimesorted.log";
/*
NOTE: If you're trying to export data to "crimesorted.log"
and then load it back into the program through "crimes.dat",
that will be a problem. I say this because the main method
in your original code, this is the order you had it in.
*/
// [BEGIN] Function Prototypes
// Structure of strings based on info from crimes.dat
struct CrimeInfo
{
string Crimedescr, Date, Time, Address,
Grid, Latitude, Longitude;
};
/** (This is JavaDoc-style documentation)
[Purpose of function here]
#param outputFile [Describe paramater here]
#param arr[] [Describe parameter here]
#param size_of_arr Size of 'arr[]'
*/
void outputLogFile(
ofstream& outputFile, // Changed to 'std::ofstream&' because I declare this in 'main()'
const CrimeInfo arr[],
const int size_of_arr);
// [END] Function Prototypes
int main()
{
// Create std::ifstream and open a file
ifstream file_to_load;
file_to_load.open(LOGFILE_NAME);
// Constructing and using 'crimeList' here allows the size to be known in
// this scope. However, if it's passed to a function, it's passed as a pointer
CrimeInfo crimeList[MAX_LOG_SIZE];
// Check if file was open and do stuff with it
if (file_to_load.is_open())
{ // File was opened
for (int i = 0; i < MAX_LOG_SIZE; i++)
{
getline(file_to_load, crimeList[i].Date);
getline(file_to_load, crimeList[i].Time);
getline(file_to_load, crimeList[i].Address);
getline(file_to_load, crimeList[i].Grid);
getline(file_to_load, crimeList[i].Crimedescr);
getline(file_to_load, crimeList[i].Latitude);
getline(file_to_load, crimeList[i].Longitude);
}
file_to_load.close(); // Close file
}
else
{ // File could not be
cout << "Could not open file: " << LOGFILE_NAME << endl;
return 1;
}
// Create std::ofstream and output the log
ofstream outputFile;
outputFile.open(OUTPUT_FILE_NAME);
// Check if 'outputFile' opened OUTPUT_FILE_NAME successfully
if(outputFile.is_open())
{ // File was opened
outputLogFile(outputFile, crimeList, MAX_LOG_SIZE);
outputFile.close();
}
else
{ // File could not be opened
cout << "Could not open file: " << OUTPUT_FILE_NAME << endl;
return 1;
}
}
// Function definition for outputLogFile()
void outputLogFile(
ofstream &outputFile,
const CrimeInfo arr[],
const int size_of_arr)
{
outputFile << "\nCrime log sort ^^:\n\n";
for (int i = 0; i < size_of_arr; i++)
{
outputFile
<< arr[i].Date << '\n' // Newlines may look better than spaces here
<< arr[i].Address << " ("
<< arr[i].Longitude << ", "
<< arr[i].Latitude << ")\n"
<< arr[i].Time << '\n'
<< arr[i].Grid << '\n'
<< arr[i].Crimedescr
<< endl;
}
}

Reading or writing binary file incorrectly

The output of the code show gibberish values for all the variables of the Student struct. When the display function is ran.
I've include the relevant code in each of the add and display function for the binary file.
For the second function, does the seekg pointer automatically move to read the the next record each time the for loop runs?
//Student struct
struct Student
{
char name [30];
float labTest;
float assignments;
float exam;
};
//Writing function
afile.open(fileName,ios::out|ios::binary);
Student S;
strcpy(S.name,"test");
S.labTest = rand()%100+1;
S.assignments = rand()%100+1;
S.exam = rand()%100+1;
afile.write(reinterpret_cast<char*>(&S),sizeof(S));
afile.close();
//Reading function
afile.open(fileName,ios::in|ios::binary);
afile.seekg(0,ios::end);
int nobyte = afile.tellg();
int recno = nobyte / sizeof(Student);
Student S;
//Loop and read every record
for(int i = 0;i<recno;i++)
{
afile.read(reinterpret_cast<char*>(&S),sizeof(S));
cout << "Name of Student: " << S.name << endl
<< "Lab mark: " << S.labTest << endl
<< "Assignment mark: " << S.assignments << endl
<< "Exam mark: " << S.exam << endl << endl;
}
afile.close();
There are a lot of problems with your code:
Calling your write function will permanently overwrite the last written data set. You have to add: ios::append, so that new data will be written behind the last data you wrote before.
After you move with afile.seekg(0,ios::end); to get with tellg the file size, you have to go back to the start of the file before reading with afile.seekg(0,ios::beg)
It looks that you use a char array to store a string. This is not c++ style! And it is dangerous how you use it. If you use strcpy, you can copy a string which is longer than the space you reserved for it. So you should prefer std::string for that. But you can't simply write a struct which constains std::string as binary! To get checked copy you can use strncpy, but that is still not c++ ;)
For the second function, does the seekg pointer automatically move to read the the next record each time the for loop runs?
Yes, the file position moves which each successful read and write.
A general remark writing binary data by simply dumping memory content:
That is not a good idea, because you can only read that data back, if you use the same machine type and the same compiler options. That means: A machine with different endianness will read data totally corrupted. Also a different integer type ( 32 bit vs 64 bit ) will break that code!
So you should invest some time how to serialize data in a portable way. There are a lot of libraries around which can be used to read/write also complex data types like std::string or container types.
A hint using SO:
Please provide code which everybody can simply cut and paste and compiled. I did not know what your Student struct is. So I take a lot of assumptions! Is your struct really using char[]? We don't know!
#include <iostream>
#include <fstream>
#include <cstring>
const char* fileName="x.bin";
struct Student
{
char name[100]; // not c++ style!
int labTest;
int assignments;
int exam;
};
// Writing function
void Write()
{
std::ofstream afile;
afile.open(fileName,std::ios::out|std::ios::binary|std::ios::app);
Student S;
strcpy(S.name,"test"); // should not be done this way!
S.labTest = rand()%100+1;
S.assignments = rand()%100+1;
S.exam = rand()%100+1;
afile.write(reinterpret_cast<char*>(&S),sizeof(S));
afile.close();
}
void Read()
{
//Reading function
std::ifstream afile;
afile.open(fileName,std::ios::in|std::ios::binary);
afile.seekg(0,std::ios::end);
int nobyte = afile.tellg();
int recno = nobyte / sizeof(Student);
afile.seekg(0, std::ios::beg);
Student S;
//Loop and read every record
for(int i = 0;i<recno;i++)
{
afile.read(reinterpret_cast<char*>(&S),sizeof(S));
std::cout << "Name of Student: " << S.name << std::endl
<< "Lab mark: " << S.labTest << std::endl
<< "Assignment mark: " << S.assignments << std::endl
<< "Exam mark: " << S.exam << std::endl << std::endl;
}
afile.close();
}
int main()
{
for ( int ii= 0; ii<10; ii++) Write();
Read();
}
EDIT. Apparently, I was a bit too late in responding. Klaus has compiled a better, more comprehensive response dwelling into other problems regarding C-style char [], std::string and the endianness of the platform.
You should append to the file opened for every record. In your code you don't have this, at all. Please write the code in a way we can copy and paste, and test. As a working example, you should write some code that can be compiled and run as below:
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
// Student struct
struct Student {
char name[30];
float labTest;
float assignments;
float exam;
};
// Serializer
void serialize_student(const Student &s, const std::string &filename) {
// Append to the file, do not overwrite it
std::ofstream outfile(filename, std::ios::binary | std::ios::app);
if (outfile)
outfile.write(reinterpret_cast<const char *>(&s), sizeof(Student));
}
// Deserializer
std::vector<Student> deserialize_students(const std::string &filename) {
std::ifstream infile(filename, std::ios::binary);
std::vector<Student> students;
Student s;
while (infile.read(reinterpret_cast<char *>(&s), sizeof(Student)))
students.push_back(std::move(s));
return std::move(students);
}
int main(int argc, char *argv[]) {
// Generate records
std::vector<Student> mystudents;
std::generate_n(std::back_inserter(mystudents), 10, []() {
Student s;
std::strcpy(s.name, "test");
s.labTest = rand() % 100 + 1;
s.assignments = rand() % 100 + 1;
s.exam = rand() % 100 + 1;
return s;
});
// Print and write the records
for (const auto &student : mystudents) {
std::cout << student.name << ": [" << student.labTest << ','
<< student.assignments << ',' << student.exam << "].\n";
serialize_student(student, "students.bin");
}
// Read and print the records
auto records = deserialize_students("students.bin");
std::cout << "===\n";
for (const auto &student : records)
std::cout << student.name << ": [" << student.labTest << ','
<< student.assignments << ',' << student.exam << "].\n";
return 0;
}

C++ Serializing a std::map to a file

I have a C++ STL map, which is a map of int and customType.
The customType is a struct, which has string and a list of string, How can i serialize this to a file.
sample struct:
struct customType{
string;
string;
int;
list<string>;
}
If you are not afraid of BOOST, try BOOST Serialize:
(template code, here can be some errors...)
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/list.hpp>
struct customType{
string string1;
string string2;
int i;
list<string> list;
// boost serialize
private:
friend class boost::serialization::access;
template <typename Archive> void serialize(Archive &ar, const unsigned int version) {
ar & string1;
ar & string2;
ar & i;
ar & list;
}
};
template <typename ClassTo>
int Save(const string fname, const ClassTo &c)
{
ofstream f(fname.c_str(), ios::binary);
if (f.fail()) return -1;
boost::archive::binary_oarchive oa(f);
oa << c;
return 0;
}
Usage:
Save< map<int, customType> >("test.map", yourMap);
A simple solution is to output each member on a line on its own, including all the strings in the list. Each record start with the key to the map, and ends with a special character or character sequence that can not be in the list. This way you can read one line at a time, and know the first line is the map key, the second line the first string in the structure and so on, and when you reach your special record-ending sequence you know the list is done and it's time for the next item in the map. This scheme makes the files generated readable, and editable if you need to edit them outside the program.
C++ doesn't have reflection capabilities like Java and others, so there's no 'automatic' way of doing that. You'll have to do all the work yourself: open the file, output each element in a loop, and close the file. Also there's no standard format for the file, you'd need to define one that meets your needs. Of course, there are libraries out there to help in this, but they aren't part of the language. Take a look at this question:
Is it possible to automatically serialize a C++ object?
Also take a look at:
http://s11n.net/
If you are asking this, then probably you already know that you cannot serialize this by means of:
file.write( (const char *) &mapOfCustom, sizeof( mapOfCustom ) );
The problem has to do with complex objects (and in C++, even a string variable is a complex object), i.e., those objects that are not self-contained. Actually, even simple serialization has problems, which range from platform compatibilty to even compiler compatibilty (different paddings, etc.).
One way to go is use a simple XML library such as tinyXML:
http://www.grinninglizard.com/tinyxml/
And write save to XML, and restore from XML procedures.
You can try this: cxx-prettyprint
Hi I wrote a standalone C11 header to achieve this. Your example
of a map of custom classes, I just added - to make sure it worked 8)
https://github.com/goblinhack/simple-c-plus-plus-serializer
#include "c_plus_plus_serializer.h"
class Custom {
public:
int a;
std::string b;
std::vector c;
friend std::ostream& operator<<(std::ostream &out,
Bits my)
{
out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
return (out);
}
friend std::istream& operator>>(std::istream &in,
Bits my)
{
in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
return (in);
}
friend std::ostream& operator<<(std::ostream &out,
class Custom &my)
{
out << "a:" << my.a << " b:" << my.b;
out << " c:[" << my.c.size() << " elems]:";
for (auto v : my.c) {
out << v << " ";
}
out << std::endl;
return (out);
}
};
static void save_map_key_string_value_custom (const std::string filename)
{
std::cout << "save to " << filename << std::endl;
std::ofstream out(filename, std::ios::binary );
std::map< std::string, class Custom > m;
auto c1 = Custom();
c1.a = 1;
c1.b = "hello";
std::initializer_list L1 = {"vec-elem1", "vec-elem2"};
std::vector l1(L1);
c1.c = l1;
auto c2 = Custom();
c2.a = 2;
c2.b = "there";
std::initializer_list L2 = {"vec-elem3", "vec-elem4"};
std::vector l2(L2);
c2.c = l2;
m.insert(std::make_pair(std::string("key1"), c1));
m.insert(std::make_pair(std::string("key2"), c2));
out << bits(m);
}
static void load_map_key_string_value_custom (const std::string filename)
{
std::cout << "read from " << filename << std::endl;
std::ifstream in(filename);
std::map< std::string, class Custom > m;
in >> bits(m);
std::cout << std::endl;
std::cout << "m = " << m.size() << " list-elems { " << std::endl;
for (auto i : m) {
std::cout << " [" << i.first << "] = " << i.second;
}
std::cout << "}" << std::endl;
}
void map_custom_class_example (void)
{
std::cout << "map key string, value class" << std::endl;
std::cout << "============================" << std::endl;
save_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
load_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
std::cout << std::endl;
}
Output:
map key string, value class
============================
save to map_of_custom_class.bin
read from map_of_custom_class.bin
m = 2 list-elems {
[key1] = a:1 b:hello c:[2 elems]:vec-elem1 vec-elem2
[key2] = a:2 b:there c:[2 elems]:vec-elem3 vec-elem4
}
Let me know if this helps - or you find bugs. It's quite a simple serializer and really just a learning tool for me. Heavier weight approaches like Cereal might work for you better.