Я сделал аналогичную программу, чтобы выяснить, что происходит. Вот как это выглядит:
class mynoncopy
{
public:
mynoncopy(int resource)
: resource(resource)
{
}
mynoncopy(mynoncopy&& other)
: resource(other.resource)
{
other.resource = 0;
}
mynoncopy(const mynoncopy& other) = delete;
mynoncopy& operator =(const mynoncopy& other) = delete;
public:
void useResource() {}
private:
int resource;
};
class mycallablevaluearg
{
public:
void operator ()(mynoncopy noncopyablething)
{
noncopyablething.useResource();
}
};
class mycallableconstrefarg
{
public:
void operator ()(const mynoncopy& noncopyablething)
{
//noncopyablething.useResource(); // can't do this becuase of const :(
}
};
class mycallablerefarg
{
public:
void operator ()(mynoncopy& noncopyablething)
{
noncopyablething.useResource();
}
};
class mycallablervaluerefarg
{
public:
void operator ()(mynoncopy&& noncopyablething)
{
noncopyablething.useResource();
}
};
class mycallabletemplatearg
{
public:
template<typename T>
void operator ()(T&& noncopyablething)
{
noncopyablething.useResource();
}
};
Когда вы запускаете std::thread(callable, std::move(thenoncopyableinstance))
, эти две вещи будут происходить внутри с использованием магии шаблона:
Создается кортеж из вашего вызываемого объекта и всех аргументов.
std::tuple<mycallablerefarg, mynoncopy> thetuple(callable,
std::move(thenoncopyableinstance));
В этом случае вызываемое копирование будет скопировано.
std::invoke()
используется для вызова вызываемого объекта, и аргумент передается ему из кортежа с использованием семантики перемещения.
std::invoke(std::move(std::get<0>(thetuple)), std::move(std::get<1>(thetuple)));
Поскольку используется семантика перемещения, ожидается, что вызываемый объект получит ссылку на rvalue в качестве аргумента (mynoncopy&&
в нашем случае). Это ограничивает нас следующими сигнатурами аргументов:
mynoncopy&&
const mynoncopy&
T&&
где T - аргумент шаблона
mynoncopy
не ссылка (это вызовет конструктор перемещения)
Вот результаты компиляции использования различных типов вызываемых элементов:
mynoncopy testthing(1337);
std::thread t(mycallablerefarg(), std::move(testthing)); // Fails, because it can not match the arguments. This is your case.
std::thread t(mycallablevaluearg(), std::move(testthing)); // OK, because the move semantics will be used to construct it so it will basically work as your solution
std::thread t(mycallableconstrefarg(), std::move(testthing)); // OK, because the argument is const reference
std::thread t(mycallablervaluerefarg(), std::move(testthing)); // OK, because the argument is rvalue reference
std::thread t(mycallabletemplatearg(), std::move(testthing)); // OK, because template deduction kicks in and gives you noncopyablething&&
std::thread t(std::bind(mycallablerefarg(), std::move(testthing))); // OK, gives you a little bit of call overhead but works. Because bind() does not seem to use move semantics when invoking the callable
std::thread t(std::bind(mycallablevalue(), std::move(testthing))); // Fails, because bind() does not use move semantics when it invokes the callable so it will need to copy the value, which it can't.