问题
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?)
回答1:
Here is some code that will work the way you want. You have to replace std::cout
s 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.
回答2:
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
回答3:
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)
来源:https://stackoverflow.com/questions/63967830/capture-a-functions-standard-output-and-write-it-to-a-file