Порождение потоков в потоке с вызываемым объектом

Я видел эту проблему несколько раз, и кажется, что она возникает как в Windoes (visual studio), так и в Linux (gcc).
Вот упрощенная версия этого:

class noncopyable
{
public:
noncopyable(int n);
~noncopyable();
noncopyable(const noncopyable&) = delete;
noncopyable& operator=(const noncopyable&) = delete;
noncopyable(noncopyable&&);
int& setvalue();
private:
int* number;
};

class thread_starter
{
public:
template<typename callable>
bool start(callable & o);
};

template<typename callable>
inline bool thread_starter::start(callable & o)
{
std::thread t(
[&]() {
int i = 10;
while (i-- > 0)
{
noncopyable m(i);
std::thread child(o, std::move(m));
child.detach();
}
});
return true;
}

class callable
{
public:
virtual void operator()(noncopyable &m);
};

void callable::operator()(noncopyable & m) { m.setvalue()++; }int main()
{
thread_starter ts;
callable o;
ts.start(o);
}

Код кажется достаточно законным, но он не скомпилируется.

В Visual Studio, это даст:

error C2893: Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)'

в НКУ, это даст:

error: no type named ‘type’ in ‘class std::result_of<callable(int)>’....

Я думаю, что я знаю, что проблема в некоторой форме механизма копирования или ссылки, но весь синтаксис, кажется, правильный.

Что мне не хватает?

Я немного изменил пример и прошу прощения за путаницу. Я пытаюсь воссоздать проблему настолько чисто, насколько это возможно, и сам не до конца ее понимаю.

0

Решение

Я сделал подобную программу, чтобы выяснить, что происходит. Вот как это выглядит:

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)) эти две вещи будут происходить внутри с использованием шаблона магии:

  1. Создается кортеж с вашим отзывом и всеми аргументами.
    std::tuple<mycallablerefarg, mynoncopy> thetuple(callable,
    std::move(thenoncopyableinstance));

    В этом случае вызываемый объект будет скопирован.

  2. std::invoke() используется для вызова вызываемого, и аргумент передается ему из кортежа с использованием семантики перемещения.
    std::invoke(std::move(std::get<0>(thetuple)), std::move(std::get<1>(thetuple)));

Поскольку используется семантика перемещения, ожидается, что вызываемый объект получит ссылку на значение в качестве аргумента (mynoncopy&& в нашем случае). Это ограничивает нас следующими сигнатурами аргументов:

  1. mynoncopy&&
  2. const mynoncopy&
  3. T&& где T — аргумент шаблона
  4. mynoncopy не ссылка (это вызовет move-constructor)

Вот результаты компиляции использования различных типов вызываемых:

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

Другие решения

Призыв к std::thread создает кортеж Кортежи нельзя инициализировать ссылками. Таким образом, вы должны использовать поддельную ссылку, std::ref(i) чтобы заставить его скомпилировать и вызвать вызываемые с int-refs int&,

template <typename callable>
bool thread_starter::start(callable &o)
{
// Nonsense
std::thread t(
[&]() {
int i = 10;
while (i-- > 0)
{
std::thread child(o, std::ref(i));
child.detach();
}
});
return true;
}

Однако полученный код не имеет смысла. Появившиеся нити конкурируют с циклом while. Цикл уменьшает индекс iв то время как потоки пытаются увеличить его. Нет никаких гарантий относительно того, когда это произойдет. Увеличение и уменьшение не являются атомарными. Поток может попытаться увеличить индекс после окончания лямбды.

Короче говоря, если вы сделаете его компиляцией, результатом будет неопределенное поведение по ряду причин.

Что ты на самом деле пытаешься сделать?

2

По вопросам рекламы [email protected]