2013-05-13 3 views
4

В чем причина того, что вы не можете переместить объект в другой поток? Бывают ситуации, когда это может быть полезно. Например:Почему невозможно переместить переменную в другой поток

Вы создаете цикл, который принимает входящие соединения сокетов. Было бы неплохо переместить входящие соединения в другой поток, который будет обрабатывать соединение. Вам больше не нужно соединение в цикле принятия. Так почему вы должны создать указатель?

Небольшой тест:

#include <iostream> 
#include <thread> 

using namespace std; 

class Pointertest 
{ 
public: 
    Pointertest() {cout << "Constructor";} 
    Pointertest(Pointertest &pointertest) {cout << "Copy";} 
    Pointertest(Pointertest &&pointertest) {cout << "Move";} 
    ~Pointertest() {cout << "Destruct";} 
}; 

void foo(Pointertest &&pointertest) 
{ 

} 

int main() 
{ 
    Pointertest pointertest; 

    foo(std::move(pointertest)); //Works 
    thread test(foo,std::move(pointertest)); //**cannot convert parameter 1 from 'Pointertest' to 'Pointertest &&'** 
} 
+1

Практический способ добиться того, что вы хотите, это использовать 'std :: thread test ([] (Pointertest & p) {foo (std :: move (p));}, std :: ref (pointertest)); 'as' std :: ref' - это инструмент политики для запроса ссылочной семантики, в которой используются семантика значения. Не существует политики для «pass in reference, restore as rvalue», поэтому необходимо, чтобы выражение lambda выполняло такое восстановление. –

ответ

15

Конструктор std::thread должен обрабатывать аргументы, которые вы даете ему несколько иначе, чем большинство функций пересылки.

Причина этого связана с вопросами о том, когда поток фактически начинается. Если часть вызова функции, которая фактически создала аргумент функции, запускается долго после создания объекта thread (что является полностью законным поведением), тогда объект, который необходимо перенести из, может быть давно уничтожен.

Просто рассмотрим измененную версию кода:

std::thread some_func() 
{ 
    Pointertest pointertest; 

    thread test(foo,std::move(pointertest)); 
    return test; 
} 

Это вполне допустимо (поток будет перемещен из функции). Однако есть большая проблема. foo, возможно, еще не был вызван. А так как foo принимает свой параметр на ссылка, теперь у него есть ссылка на переменную стека, которая была уничтожена ..

Плохо. Но даже если foo взял свой параметр по значению, он ничего не изменит. Поскольку фактическое перемещение в этот параметр происходит не до некоторого неопределенного времени после начала потока. Попытка перейти к параметру по-прежнему будет использовать ссылку rvalue для уничтоженной переменной стека. Что опять плохо.

Следовательно, std::thread конструктор делает что-то другое. Он копирует/перемещает аргументы, которые вы передаете во внутреннюю память (это делается в текущем потоке). Затем он использует эти значения в качестве аргументов для фактического вызова функции (это делается в новом потоке).

В соответствии со стандартом конструктор потока должен обрабатывать эти внутренние переменные для ваших функций как временные. В стандарте конкретно указывается INVOKE (DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...), где в исходной цепочке происходит событие DECAY_COPY, а часть INVOKE - в новой ветке.

Похоже, что ваша реализация thread не в состоянии переслать несжатые параметры правильно. Вы должны быть в состоянии передать тип, не подлежащий копированию; аргументы должны быть только MoveConstructible.

Так что это будет ошибкой в ​​вашей реализации.

5

Это возможно. Закрепление подписи конструктора копирования делает это работает для меня:

class Pointertest 
{ 
public: 
    Pointertest() {cout << "Constructor";} 
    Pointertest(Pointertest const& pointertest) {cout << "Copy";} 
//       ^^^^^^ 
    Pointertest(Pointertest &&pointertest) {cout << "Move";} 
    ~Pointertest() {cout << "Destruct";} 
}; 

Кроме того, не забудьте присоединиться к нити (или отсоединить от него) перед вашим thread объектом выходит из области видимости:

int main() 
{ 
    Pointertest pointertest; 
    thread test(foo, std::move(pointertest)); 

    test.join(); 
// ^^^^^^^^^^^^ 
} 
+0

Я не хочу копировать объект; Я только хочу его переместить. Деструктор нельзя назвать. Я получаю ту же ошибку с вашей коррекцией. – Ordo

+3

@Ordo: «движение» не означает, что деструктор никогда не вызван. Когда вы переходите от объекта, вы должны оставить его в состоянии, которое все еще функционирует * (так, что это деструктор работает). Но вы взяли из него все важные бит. В каждом конструкторе по-прежнему будет следовать деструктор; движение не мешает этому. –

+0

@Nicol Bolas Возможно ли тогда определить, является ли это движением? Деструктор не вызывается, когда я переношу объект на другую функцию. – Ordo

Смежные вопросы