A resposta de Herb (antes de ser editado) realmente deu um bom exemplo de um tipo que não deve ser móvel: std::mutex
.
O tipo mutex nativo do sistema operacional (por exemplo, pthread_mutex_t
nas plataformas POSIX) pode não ser "invariável no local", o que significa que o endereço do objeto faz parte do seu valor. Por exemplo, o sistema operacional pode manter uma lista de ponteiros para todos os objetos mutex inicializados. Se std::mutex
contivesse um tipo de mutex do SO nativo como membro de dados e o endereço do tipo nativo std::mutex
devesse permanecer fixo (porque o SO mantém uma lista de ponteiros para os mutexes), seria necessário armazenar o tipo de mutex nativo no heap para que ele permanecesse em o mesmo local quando movido entre std::mutex
objetos ou o std::mutex
não deve se mover. Não é possível armazená-lo no heap, porque a std::mutex
possui um constexpr
construtor e deve ser elegível para inicialização constante (ou seja, inicialização estática) para que um globalstd::mutex
é garantido para ser construído antes do início da execução do programa, portanto, seu construtor não pode usá-lo new
. Portanto, a única opção que resta é std::mutex
ser imóvel.
O mesmo raciocínio se aplica a outros tipos que contêm algo que requer um endereço fixo. Se o endereço do recurso precisar permanecer fixo, não o mova!
Há outro argumento para não se mexer std::mutex
: seria muito difícil fazê-lo com segurança, porque você precisaria saber que ninguém está tentando bloquear o mutex no momento em que está sendo movido. Como os mutexes são um dos blocos de construção que você pode usar para evitar corridas de dados, seria lamentável se eles não estivessem seguros contra as próprias corridas! Com um imóvel, std::mutex
você sabe que as únicas coisas que alguém pode fazer depois que ele for construído e antes de ser destruído é bloqueá-lo e desbloqueá-lo, e essas operações garantem explicitamente a segurança de threads e a não introduzir corridas de dados. Esse mesmo argumento se aplica aos std::atomic<T>
objetos: a menos que eles possam ser movidos atomicamente, não seria possível movê-los com segurança, outro encadeamento pode estar tentando chamarcompare_exchange_strong
no objeto no momento em que está sendo movido. Portanto, outro caso em que os tipos não devem ser móveis é onde eles são blocos de construção de baixo nível de código simultâneo seguro e devem garantir a atomicidade de todas as operações neles. Se o valor do objeto puder ser movido para um novo objeto a qualquer momento, você precisará usar uma variável atômica para proteger todas as variáveis atômicas, para saber se é seguro usá-lo ou se foi movido ... e uma variável atômica para proteger essa variável atômica, e assim por diante ...
Eu acho que generalizaria para dizer que quando um objeto é apenas um pedaço de memória pura, não um tipo que atua como detentor de um valor ou abstração de um valor, não faz sentido movê-lo. Tipos fundamentais como int
não podem se mover: movê-los é apenas uma cópia. Você não pode arrancar as tripas de um int
, pode copiar seu valor e depois defini-lo como zero, mas ainda é um int
com um valor, são apenas bytes de memória. Mas um int
ainda é móvelnos termos do idioma porque uma cópia é uma operação de movimentação válida. No entanto, para tipos não copiáveis, se você não deseja ou não pode mover o pedaço de memória e também não pode copiar seu valor, ele não é móvel. Um mutex ou uma variável atômica é um local específico da memória (tratado com propriedades especiais), portanto, não faz sentido mover-se e também não é copiável, portanto, não é móvel.