Fiquei surpreso que isso não tenha aparecido nos meus resultados de pesquisa, pensei que alguém teria perguntado isso antes, dada a utilidade da semântica de movimento no C ++ 11:
Quando tenho que (ou é uma boa idéia para mim) tornar uma classe não móvel no C ++ 11?
( Outros motivos que não problemas de compatibilidade com o código existente).
c++
c++11
move-semantics
c++-faq
user541686
fonte
fonte
+1
de mim), com uma resposta muito completa de Herb (ou de seu irmão gêmeo, ao que parece ), então fiz uma entrada na FAQ. Se alguém me fizer ping apenas no lounge , isso poderá ser discutido lá.T x = std::move(anotherT);
ser legal" não são equivalentes. O último é uma solicitação de movimentação que pode recorrer ao copiador no caso de T não ter um movedor. Então, o que significa "móvel" exatamente?Respostas:
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. Sestd::mutex
contivesse um tipo de mutex do SO nativo como membro de dados e o endereço do tipo nativostd::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 entrestd::mutex
objetos ou ostd::mutex
não deve se mover. Não é possível armazená-lo no heap, porque astd::mutex
possui umconstexpr
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á-lonew
. 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 aosstd::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 umint
, pode copiar seu valor e depois defini-lo como zero, mas ainda é umint
com um valor, são apenas bytes de memória. Mas umint
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.fonte
Resposta curta: Se um tipo é copiável, também deve ser móvel. No entanto, o contrário não é verdadeiro: alguns tipos
std::unique_ptr
são móveis, mas não faz sentido copiá-los; esses são naturalmente tipos somente de movimentação.Segue uma resposta um pouco mais longa ...
Existem dois tipos principais de tipos (entre outros de propósito mais específico, como características):
Tipos de valor, como
int
ouvector<widget>
. Eles representam valores e devem ser naturalmente copiáveis. No C ++ 11, geralmente você deve pensar em mover-se como uma otimização de cópia e, portanto, todos os tipos copiáveis devem ser naturalmente móveis. não precisa mais do objeto original e apenas o destruirá de qualquer maneira.Tipos de referência que existem em hierarquias de herança, como classes base e classes com funções-membro virtuais ou protegidas. Normalmente, eles são mantidos por ponteiro ou referência, geralmente um
base*
oubase&
, e portanto não fornecem construção de cópia para evitar o fatiamento; se você deseja obter outro objeto como um existente, geralmente chama uma função virtual comoclone
. Eles não precisam de construção ou atribuição de movimento por dois motivos: eles não são copiáveis e já possuem uma operação natural de "movimento" ainda mais eficiente - basta copiar / mover o ponteiro para o objeto e o próprio objeto não. precisa mudar para um novo local de memória.A maioria dos tipos se enquadra em uma dessas duas categorias, mas também existem outros tipos que também são úteis, apenas mais raros. Em particular aqui, tipos que expressam propriedade exclusiva de um recurso, como
std::unique_ptr
, naturalmente, são apenas para movimentação, porque não são semelhantes a valores (não faz sentido copiá-los), mas você os usa diretamente (nem sempre por ponteiro ou referência) e, portanto, deseja mover objetos desse tipo de um lugar para outro.fonte
std::mutex
era imóvel, como mutexes POSIX são usados por endereço.Na verdade, quando procuro, descobri que alguns tipos no C ++ 11 não são móveis:
mutex
tipos (recursive_mutex
,timed_mutex
,recursive_timed_mutex
,condition_variable
type_info
error_category
locale::facet
random_device
seed_seq
ios_base
basic_istream<charT,traits>::sentry
basic_ostream<charT,traits>::sentry
atomic
tiposonce_flag
Aparentemente, há uma discussão sobre Clang: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4
fonte
iterators / iterator adaptors
deve ser editado como C ++ 11 tem move_iterator?std::reference_wrapper
. Ok, os outros realmente parecem ser móveis.ios_base
,type_info
,facet
), 3. coisas estranhas sortidas (sentry
). Provavelmente, as únicas classes imutáveis que um programador médio escreverá estão na segunda categoria.Outra razão que eu encontrei - desempenho. Digamos que você tenha uma classe 'a' que possui um valor. Você deseja gerar uma interface que permita ao usuário alterar o valor por um tempo limitado (para um escopo).
Uma maneira de conseguir isso é retornando um objeto 'protetor de escopo' de 'a' que retorna o valor em seu destruidor, da seguinte maneira:
Se eu fizesse o change_value_guard móvel, teria que adicionar um 'if' ao seu destruidor para verificar se a proteção foi removida - isso é um extra se e um impacto no desempenho.
Sim, claro, provavelmente pode ser otimizado por qualquer otimizador sensato, mas ainda assim é bom que a linguagem (isso requer C ++ 17, porém, para poder retornar um tipo não móvel exija garantia de cópia) não nos exija pagar isso se, se não quisermos mudar a proteção, a não ser devolvê-la da função de criação (o princípio de não pagar pelo que você não usa).
fonte