Search code examples
c++multithreadingc++11ofstream

Passing output stream object and other arguments to multiple threads


I'm trying to pass multiple arguments, one of which is an ofstream object, to multiple threads using the C++11 standard.

I want to pass an ofstream object because I want every thread to write in a different output file.

I'm initializing the threads and output streams in this way:

std::thread execution_threads[NUMBER_OF_THREADS]; // creating multiple empty threads
std::ofstream output_files[NUMBER_OF_THREADS]; // Empty output streams
// Opening and initializing the output files

Each thread executes a function that takes two arguments:

void execution(int thread_id, std::ofstream& output_file)

So I've looked around and I've read that in C++11 when a function func has multiple arguments a,b,c,d there's no need to use a struct and you can pass them just by writing std::thread t(func, a,b,c,d);. So I wrote this loop in order to launch the threads:

for (int i = 0; i < utils::NUMBER_OF_THREADS; i++) {
    execution_threads[i] = std::thread(execution, i, output_files[i]);
}

The thing is that this code doesn't compile with this error:

Call to implicitly-deleted copy constructor of
'typename decay<basic_ofstream<char, char_traits<char> > &>::type'
(aka 'std::__1::basic_ofstream<char, std::__1::char_traits<char> >')

While if I use a struct as input in this way, everything works fine:

// Struct definition
struct thread_input {
    int thread_id;
    std::ofstream& output_file;
};

// This is the function that now has only one argument
void execution(struct thread_input input)

// And this is the loop that launches the threads
for (int i = 0; i < utils::NUMBER_OF_THREADS; i++) {
    struct thread_input input = {i, output_files[i]};
    execution_threads[i] = std::thread(execution, input);
}

// join() functions for each thread and end of the program

In this way everything works fine it compiles and it runs perfectly. But I really don't get why the compiler tells me that I'm trying to use the deleted copy-constructor if I use the other method.

Thank you for your help.


Solution

  • std::thread stores a copy of its arguments. When you pass it a non-copyable object like a std::ofstream it will complain.

    You have two options:

    1) Don't store an array of std::ofstream objects; just let your threads store their own streams. In this case, there's no need to copy the stream (just a move, which is fine):

    for (int i = 0; i < utils::NUMBER_OF_THREADS; i++) {
        execution_threads[i] = std::thread(execution, i, std::ofstream{});
                                                       //^^^^^^^^^^^^^^^ anonymous temporary
    }
    

    Of course, in this case you could just have the thread construct its own stream (maybe just pass in the filename).

    2) Pass a std::reference_wrapper<std::ofstream> to your thread. std::reference_wrapper<T> is an object that holds a reference to T and has an implicit conversion to T&, so you'll just end up copying the reference instead of the stream itself. You can use the std::ref factory to deduce T and reduce typing:

    for (int i = 0; i < utils::NUMBER_OF_THREADS; i++) {
        execution_threads[i] = std::thread(execution, i, std::ref(output_files[i]));
                                                       //^^^^^^^^ std::ref added
    }
    

    This leaves you with all of the same ownership and lifetime issues that passing a struct containing a std::ofstream& would have (after all, that's all std::reference_wrapper is). It's up to you to make sure your output_files array survives until all of your threads have finished with it.