Capture a functions standard output and write it to a file

纵饮孤独 提交于 2021-02-05 07:22:26

问题


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::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.




回答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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!