How to close an std::ofstream that has been passed by const reference through a thread to a function that takes an std::ostream const&?

Viewed 57

I have a (set) of function(s) that take in an std::ostream& so that I can change where they will output. These are passed into a control function that runs them at a certain time each day, and each instance of control function runs and is instantiated in it's own std::thread. In order to pass an std::ofstream to the inner worker functions, they must be passed as a (stream typename) const& (this is the only way possible with threads but I don't remember exactly why). Because I did not want to implement the code that converts the std::ostream const& back to a useable reference within the function, I also created yet another wrapper function that does this and then passes the necessary parameters to the worker functions.

The control function

//"DayTime" is a struct containing the target time of day
template<typename returntype, typename... args>
void routineThread(std::atomic_bool const& control, const DayTime tme, const time_t updateinterval, returntype(*func)(args...), args... arg) {
    std::atomic_bool& con = const_cast<std::atomic_bool&>(control);
    while (con) {
        //check if control bool has updated every update interval
        //"d_untilNext" returns the number of seconds until the target time of day
        while (updateinterval < d_untilNext(tme)) {
            if (!con) {
                break;
            }
            std::this_thread::sleep_for(std::chrono::seconds(updateinterval));
        }
        if (con) {
            //"d_nextTime" returns an "std::chrono::time_point" for the target time of day
            std::this_thread::sleep_until(d_nextTime(tme));
            func(arg...);
            //wait until target TOD has passed, otherwise the function will be run multiple times
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }
}

The stream converter and "templatefunc"

typedef void(*templatefunc)(const char*, std::ostream&);

void streamWrapper(const char* message, std::ostream const& output, templatefunc func) {
    std::ostream& out = const_cast<std::ostream&>(output);
    if (typeid(output) == typeid(std::ofstream)) {
        std::ofstream fout;
        fout.basic_ios<char>::rdbuf(out.rdbuf());
        func(message, fout);
        fout.close();
    }
    else {
        func(message, out);
    }
}

An example worker function

void work(const char* message, std::ostream& output) {
    output << dateStamp() << ": work done?" << newline; //dateStamp() -> returns string with date and time
}

An example std::thread implementation

std::atomic_bool run = { true }; //this value is manipulated elsewhere

std::thread th(routineThread < void, const char*, std::ostream const&, templatefunc > , std::ref(run), streamWrapper, "message", std::fstream("/data/logs/work.txt", std::ios::trunc), work);

My problem is that when an std::fstream parameter is supplied (into the thread constructor, which passes it to the control function, which then passes it to the stream wrapper function), the constructor opens the file I am trying to log to and stays open until the end of the thread is ended. Since the stream has been passed to an std::ostream& parameter, I cannot close or manipulate the original object at all, and the file stays open within that object until the thread ends (the end of the program), thus any output that the inner worker functions have submitted is either not written to the file or is only written when the thread is ending and the original fstream object is deleted. This makes for some pretty useless logging, as the program is meant to run continuously (although it is occasionally restarted). Is there is anything that I could change to allow the original stream to be closed, while retaining the flexibility of an std::ostream& parameter? Again, the goal is to log to a file and for the output to be visible immediately, instead of appearing only when the program ends. I know that I could just change the parameter to only take an std::ofstream&, but I only want to do this if it is the only option.

0 Answers
Related