claudius
claudius

Reputation: 1144

C++11 thread compilation error, deleted copy constructor and std::thread, why?

This emits some 100 line error. error: use of deleted function of run_me copy constructor and so on. So, what's the problem here?

#include<thread>
#include<iostream>  
#include<vector>
#include<fstream>
#include<string>
#include<chrono>
using namespace std;
//creating thread to print line from file
class run_me {
    ifstream fs;
    public:
    string s;
        run_me(): fs("lliftOff.cpp",ifstream::in) {}
    void operator()(){
        cout<<"hi";
        while(getline(fs,s) ) {
            cout<<s<<endl;
        this_thread::sleep_for (chrono::seconds(1));
        }
    }
    ~run_me() {
        fs.close();
    }
};
int main() {
    thread t1{run_me()};
t1.join();
cout<<"shutting down now"<<endl;
}

Upvotes: 2

Views: 2328

Answers (2)

Filip Ros&#233;en
Filip Ros&#233;en

Reputation: 63807

THE EXPLANATION

The Standard mandates that the object, and the parameters, passed to the relevant constructor of std::thread must be MoveConstructible.

The problem with the given code is that class run_me has an implicitly deleted copy/move-constructor since it has a non-copyable member; std::ifstream fs.


SOLUTION #1 - CHANGE THE CALL SITE

To prevent the compiler from trying to copy class run_me all we have to do is wrapping the object inside a lambda and initialize our std::thread with this said object:

std::thread t1 {
  [] { run_me {} (); }
};

Note: the above creates a temporary run_me {} and calls operator() on it inside an anonymous lambda, the temporary run me {} will be created when the lambdas function is actually called, not when the lambda is created. When t1 calls anonymous-lambda () the function scope will be entered, and run_me {} will be initialized.


SOLUTION #2 - CHANGE class run me

Normally we would just add a move-constructor to class run_me that would initialize fs with a std::moved instance of the original fs, not really a big issue.

Sadly implementations such as libstdc++ hasn't implemented the functionality required to properly move streams, this means that we cannot move Standard library streams around, yet.

If you are working with libstdc++, or any other implementation that lacks movable streams, we will have to resort to some sort of hack when writing our solution, and I recommend the below:

#include <iostream>
#include <fstream>
#include <string>
#include <memory>

class run_me {
    std::unique_ptr<std::ifstream> fs;

    public:
    string s;

    run_me()
      : fs (new std::ifstream ("foo.cpp",ifstream::in))
    { }

    run_me (run_me&& src)
      : fs (std::move (src.fs))
    { }

    void operator()() {
      while(getline(*fs,s) ) {
        cout<<s<<endl;
        this_thread::sleep_for (chrono::seconds(1));
      }
    }

    ~run_me() {
      if (fs) // if we haven't been moved-from
        fs->close();
    }
};

Upvotes: 4

galop1n
galop1n

Reputation: 8824

your ifstream implementation lack support for move operation, still deleting copy operation.

You could use a lambda to delay the run_me creation inside the thread

std::thread t { [] { run_me{}(); } };

If you wants to keep the run_me creation before the thread start, you could use std::ref

run_me rm;
thread t1{ []( run_me & rm ) { rm(); }, std::ref(rm) };

Upvotes: 5

Related Questions