O que são as semânticas de movimento?

Acabei de ouvir a entrevista do podcast de rádio de engenharia de software com Scott Meyers sobre C ++ 0x . A maioria dos novos resources fez sentido para mim e estou realmente animado com C ++ 0x agora, com exceção de um. Eu ainda não consigo mover a semântica … O que eles são exatamente?

Acho mais fácil entender a semântica de movimento com código de exemplo. Vamos começar com uma class de strings muito simples que apenas contém um ponteiro para um bloco de memory alocado para heap:

#include  #include  class string { char* data; public: string(const char* p) { size_t size = strlen(p) + 1; data = new char[size]; memcpy(data, p, size); } 

Como escolhemos gerenciar a memory por nós mesmos, precisamos seguir a regra de três . Eu vou adiar a gravação do operador de atribuição e implementar apenas o destrutor e o construtor de cópia por enquanto:

  ~string() { delete[] data; } string(const string& that) { size_t size = strlen(that.data) + 1; data = new char[size]; memcpy(data, that.data, size); } 

O construtor de cópia define o que significa copiar objects de string. O parâmetro const string& that se liga a todas as expressões do tipo string que permite fazer cópias nos seguintes exemplos:

 string a(x); // Line 1 string b(x + y); // Line 2 string c(some_function_returning_a_string()); // Line 3 

Agora vem a introspecção chave em mover a semântica. Note que somente na primeira linha em que copiamos x essa cópia profunda é realmente necessária, porque poderíamos querer inspecionar x mais tarde e ficaria muito surpreso se x tivesse mudado de alguma forma. Você notou como eu acabei de dizer x três vezes (quatro vezes se você include esta frase) e quis dizer exatamente o mesmo object toda vez? Nós chamamos expressões como x “lvalues”.

Os argumentos nas linhas 2 e 3 não são lvalues, mas rvalues, porque os objects string subjacentes não possuem nomes, portanto, o cliente não tem como inspecioná-los novamente em um momento posterior. rvalues ​​denotam objects temporários que são destruídos no próximo ponto e vírgula (para ser mais preciso: no final da expressão completa que contém lexicamente o rvalue). Isso é importante porque durante a boot de b , nós poderíamos fazer o que quiséssemos com a string de origem, e o cliente não poderia dizer a diferença !

C ++ 0x introduz um novo mecanismo chamado “referência rvalue” que, entre outras coisas, nos permite detectar argumentos rvalue via sobrecarga de function. Tudo o que precisamos fazer é escrever um construtor com um parâmetro de referência rvalue. Dentro desse construtor, podemos fazer o que quisermos com a fonte, desde que a deixemos em algum estado válido:

  string(string&& that) // string&& is an rvalue reference to a string { data = that.data; that.data = nullptr; } 

O que fizemos aqui? Em vez de copiar profundamente os dados do heap, acabamos de copiar o ponteiro e, em seguida, definimos o ponteiro original como nulo. Na verdade, nós “roubamos” os dados que originalmente pertenciam à string de origem. Novamente, a principal ideia é que sob nenhuma circunstância o cliente poderia detectar que a fonte havia sido modificada. Como não fazemos uma cópia aqui, chamamos esse construtor de “construtor de movimento”. Sua tarefa é mover resources de um object para outro em vez de copiá-los.

Parabéns, você agora entende os fundamentos da semântica de movimento! Vamos continuar implementando o operador de atribuição. Se você não estiver familiarizado com o idioma de copiar e trocar , aprenda e volte, porque é uma ótima linguagem de C ++ relacionada à segurança de exceções.

  string& operator=(string that) { std::swap(data, that.data); return *this; } }; 

É isso? “Onde está a referência de valor?” você pode perguntar. “Não precisamos disso aqui!” é minha resposta 🙂

Note que nós passamos o parâmetro that por valor , de modo that tem que ser inicializado como qualquer outro object de string. Exatamente como that vai ser inicializado? Nos velhos tempos do C ++ 98 , a resposta teria sido “pelo construtor de cópia”. Em C ++ 0x, o compilador escolhe entre o construtor de cópia e o construtor de movimento com base em se o argumento para o operador de atribuição é um lvalue ou um rvalue.

Portanto, se você disser a = b , o construtor de cópias inicializará that (porque a expressão b é um lvalue) e o operador de atribuição trocará o conteúdo por uma cópia profunda recém-criada. Essa é a própria definição do idioma de copiar e trocar – faça uma cópia, troque o conteúdo pela cópia e, em seguida, elimine a cópia deixando o escopo. Nada de novo aqui.

Mas se você disser a = x + y , o construtor de movimento inicializará that (porque a expressão x + y é um valor r), portanto, não há cópia profunda envolvida, apenas um movimento eficiente. that ainda é um object independente do argumento, mas sua construção era trivial, já que os dados do heap não precisavam ser copiados, apenas movidos. Não foi necessário copiá-lo porque x + y é um rvalue e, novamente, não há problema em mover de objects string indicados por rvalues.

Para resumir, o construtor de cópia faz uma cópia profunda, porque a origem deve permanecer intocada. O construtor de movimento, por outro lado, pode simplesmente copiar o ponteiro e, em seguida, definir o ponteiro na origem como nulo. Não há problema em “anular” o object de origem dessa maneira, porque o cliente não tem como inspecionar o object novamente.

Espero que este exemplo tenha atingido o ponto principal. Há muito mais para valorizar as referências e mover a semântica que deixei intencionalmente de lado para mantê-la simples. Se você quiser mais detalhes, consulte minha resposta suplementar .

Minha primeira resposta foi uma introdução extremamente simplificada para mover semântica, e muitos detalhes foram deixados de propósito para mantê-lo simples. No entanto, há muito mais para mover a semântica, e achei que era hora de uma segunda resposta para preencher as lacunas. A primeira resposta já é bastante antiga, e não parecia certo simplesmente substituí-la por um texto completamente diferente. Eu acho que ainda serve bem como uma primeira introdução. Mas se você quiser ir mais fundo, continue lendo 🙂

Stephan T. Lavavej levou o tempo fornecer feedback valioso. Muito obrigado Stephan!

Introdução

Mover semântica permite que um object, sob certas condições, assuma os resources externos de outros objects. Isso é importante de duas maneiras:

  1. Transformar cópias caras em movimentos baratos. Veja minha primeira resposta para um exemplo. Observe que se um object não gerenciar pelo menos um recurso externo (direta ou indiretamente por meio de seus objects membros), a semântica de movimentação não oferecerá nenhuma vantagem sobre a semântica de cópia. Nesse caso, copiar um object e mover um object significa exatamente a mesma coisa:

     class cannot_benefit_from_move_semantics { int a; // moving an int means copying an int float b; // moving a float means copying a float double c; // moving a double means copying a double char d[64]; // moving a char array means copying a char array // ... }; 
  2. Implementando tipos seguros “somente de movimento”; isto é, tipos para os quais copiar não faz sentido, mas o movimento faz. Os exemplos incluem bloqueios, identificadores de arquivos e pointers inteligentes com semântica de propriedade exclusiva. Nota: Esta resposta discute std::auto_ptr , um modelo de biblioteca padrão C ++ 98 obsoleto, que foi substituído por std::unique_ptr em C ++ 11. Programadores C ++ intermediários provavelmente estão pelo menos um pouco familiarizados com std::auto_ptr , e por causa da “semântica de movimento” exibida, parece ser um bom ponto de partida para discutir a semântica de movimento em C ++ 11. YMMV.

O que é um movimento?

A biblioteca padrão do C ++ 98 oferece um ponteiro inteligente com uma semântica de propriedade exclusiva chamada std::auto_ptr . Caso você não esteja familiarizado com o auto_ptr , seu objective é garantir que um object alocado dinamicamente seja sempre liberado, mesmo em caso de exceções:

 { std::auto_ptr a(new Triangle); // ... // arbitrary code, could throw exceptions // ... } // < --- when a goes out of scope, the triangle is deleted automatically 

O incomum sobre auto_ptr é seu comportamento de "copiar":

 auto_ptr a(new Triangle); +---------------+ | triangle data | +---------------+ ^ | | | +-----|---+ | +-|-+ | a | p | | | | | +---+ | +---------+ auto_ptr b(a); +---------------+ | triangle data | +---------------+ ^ | +----------------------+ | +---------+ +-----|---+ | +---+ | | +-|-+ | a | p | | | b | p | | | | | +---+ | | +---+ | +---------+ +---------+ 

Observe como a boot de b com a não copia o triângulo, mas transfere a propriedade do triângulo de a para b . Também dizemos " a é movido para b " ou "o triângulo é movido de a para b ". Isso pode soar confuso, porque o próprio triângulo sempre permanece no mesmo lugar na memory.

Mover um object significa transferir a propriedade de algum recurso que ele gerencia para outro object.

O construtor de cópia de auto_ptr provavelmente se parece com algo assim (um pouco simplificado):

 auto_ptr(auto_ptr& source) // note the missing const { p = source.p; source.p = 0; // now the source no longer owns the object } 

Movimentos perigosos e inofensivos

A coisa perigosa sobre o auto_ptr é que o que sintaticamente se parece com uma cópia é realmente um movimento. Tentar chamar uma function de membro em um auto_ptr movido de invocará um comportamento indefinido, portanto, você deve ter muito cuidado para não usar um auto_ptr após ele ter sido movido de:

 auto_ptr a(new Triangle); // create triangle auto_ptr b(a); // move a into b double area = a->area(); // undefined behavior 

Mas o auto_ptr nem sempre é perigoso. As funções de fábrica são um caso de uso perfeitamente auto_ptr para auto_ptr :

 auto_ptr make_triangle() { return auto_ptr(new Triangle); } auto_ptr c(make_triangle()); // move temporary into c double area = make_triangle()->area(); // perfectly safe 

Observe como os dois exemplos seguem o mesmo padrão sintático:

 auto_ptr variable(expression); double area = expression->area(); 

E, no entanto, um deles invoca um comportamento indefinido, enquanto o outro não. Então, qual é a diferença entre as expressões a e make_triangle() ? Não são ambos do mesmo tipo? Na verdade eles são, mas eles têm diferentes categorias de valor .

Categorias de valor

Obviamente, deve haver alguma diferença profunda entre a expressão a que denota uma variável auto_ptr e a expressão make_triangle() que denota a chamada de uma function que retorna um auto_ptr por valor, criando assim um novo object temporário auto_ptr toda vez que é chamado . a é um exemplo de um lvalue , enquanto make_triangle() é um exemplo de um rvalue .

Mover-se de lvalores como a é perigoso, porque poderíamos mais tarde tentar chamar uma function de membro por meio de a , invocando um comportamento indefinido. Por outro lado, mover de valores make_triangle() como make_triangle() é perfeitamente seguro, porque depois que o construtor de cópia fez seu trabalho, não podemos usar o temporário novamente. Não há expressão que denota o dito temporário; se simplesmente escrevermos make_triangle() novamente, obteremos um temporário diferente . Na verdade, o temporário movido de já se foi na próxima linha:

 auto_ptr c(make_triangle()); ^ the moved-from temporary dies right here 

Observe que as letras l e r têm uma origem histórica no lado esquerdo e no lado direito de uma atribuição. Isso não é mais verdadeiro em C ++, porque existem lvalores que não podem aparecer no lado esquerdo de uma atribuição (como matrizes ou tipos definidos pelo usuário sem um operador de atribuição), e existem valores r que podem (todos os valores de tipos de class) com um operador de atribuição).

Um rvalue do tipo de class é uma expressão cuja avaliação cria um object temporário. Em circunstâncias normais, nenhuma outra expressão dentro do mesmo escopo indica o mesmo object temporário.

Referências de valor

Agora entendemos que sair dos valores é potencialmente perigoso, mas sair dos valores é inofensivo. Se o C ++ possuísse suporte a idiomas para distinguir argumentos lvalue de argumentos rvalue, poderíamos proibir completamente a mudança de lvalores, ou pelo menos fazer a mudança de lvalores explícitos no local da chamada, para que não nos movêssemos mais por acidente.

A resposta do C ++ 11 para este problema é de referências rvalue . Uma referência rvalue é um novo tipo de referência que apenas se liga a rvalues, e a syntax é X&& . A boa referência antiga X& é agora conhecida como uma referência de lvalue . (Note que X&& não é uma referência a uma referência; não existe tal coisa em C ++.)

Se lançarmos const na mistura, já temos quatro tipos diferentes de referências. Que tipos de expressões do tipo X eles podem se vincular?

  lvalue const lvalue rvalue const rvalue --------------------------------------------------------- X& yes const X& yes yes yes yes X&& yes const X&& yes yes 

Na prática, você pode esquecer do const X&& . Estar restrito a ler de valores não é muito útil.

Uma referência de valor X&& é um novo tipo de referência que apenas se liga a valores de r.

Conversões implícitas

Referências de valor passaram por várias versões. Desde a versão 2.1, uma referência rvalue X&& também se liga a todas as categorias de valor de um tipo diferente Y , desde que haja uma conversão implícita de Y para X Nesse caso, um temporário do tipo X é criado e a referência rvalue é ligada a esse temporário:

 void some_function(std::string&& r); some_function("hello world"); 

No exemplo acima, "hello world" é um lvalue do tipo const char[12] . Como há uma conversão implícita de const char[12] através de const char* para std::string , um temporário do tipo std::string é criado, e r é ligado a esse temporário. Este é um dos casos em que a distinção entre valores (expressões) e temporários (objects) é um pouco embaçada.

Mover construtores

Um exemplo útil de uma function com um parâmetro X&& é o construtor de movimento X::X(X&& source) . Sua finalidade é transferir a propriedade do recurso gerenciado da origem para o object atual.

Em C ++ 11, std::auto_ptr foi substituído por std::unique_ptr que aproveita as referências rvalue. Eu desenvolverei e discutirei uma versão simplificada de unique_ptr . Primeiro, encapsulamos um ponteiro bruto e sobrecarregamos os operadores -> e * , então nossa class parece um ponteiro:

 template class unique_ptr { T* ptr; public: T* operator->() const { return ptr; } T& operator*() const { return *ptr; } 

O construtor apropria-se do object e o destruidor o exclui:

  explicit unique_ptr(T* p = nullptr) { ptr = p; } ~unique_ptr() { delete ptr; } 

Agora vem a parte interessante, o construtor de movimento:

  unique_ptr(unique_ptr&& source) // note the rvalue reference { ptr = source.ptr; source.ptr = nullptr; } 

Este construtor de movimento faz exatamente o que o auto_ptr cópia auto_ptr fez, mas só pode ser fornecido com valores de r:

 unique_ptr a(new Triangle); unique_ptr b(a); // error unique_ptr c(make_triangle()); // okay 

A segunda linha falha na compilation, porque a é um lvalue, mas o parâmetro unique_ptr&& source só pode ser ligado a rvalues. Isso é exatamente o que nós queríamos; movimentos perigosos nunca devem ser implícitos. A terceira linha compila muito bem, porque make_triangle() é um rvalue. O construtor de movimento transferirá a propriedade do temporário para c . Mais uma vez, isso é exatamente o que queríamos.

O construtor de movimento transfere a propriedade de um recurso gerenciado para o object atual.

Mover operadores de atribuição

A última peça que falta é o operador de atribuição de movimento. Sua tarefa é liberar o recurso antigo e adquirir o novo recurso a partir de seu argumento:

  unique_ptr& operator=(unique_ptr&& source) // note the rvalue reference { if (this != &source) // beware of self-assignment { delete ptr; // release the old resource ptr = source.ptr; // acquire the new resource source.ptr = nullptr; } return *this; } }; 

Observe como essa implementação do operador de atribuição de movimentação duplica a lógica do destruidor e do construtor de movimentação. Você está familiarizado com o idioma de cópia e troca? Também pode ser aplicado para mover a semântica como o idioma de movimentação e troca:

  unique_ptr& operator=(unique_ptr source) // note the missing reference { std::swap(ptr, source.ptr); return *this; } }; 

Agora que source é uma variável do tipo unique_ptr , ele será inicializado pelo construtor move; isto é, o argumento será movido para o parâmetro. O argumento ainda é necessário para ser um rvalue, porque o próprio construtor de movimento possui um parâmetro de referência rvalue. Quando o stream de controle atinge a chave de fechamento de operator= , a source sai do escopo, liberando o recurso antigo automaticamente.

O operador de atribuição de movimentação transfere a propriedade de um recurso gerenciado para o object atual, liberando o recurso antigo. O idioma de movimentação e troca simplifica a implementação.

Mover-se de lvalores

Às vezes, queremos passar de lvalues. Isto é, às vezes queremos que o compilador trate um lvalue como se fosse um rvalue, então ele pode invocar o construtor de movimento, mesmo que ele possa ser potencialmente inseguro. Para este propósito, o C ++ 11 oferece um modelo de function de biblioteca padrão chamado std::move dentro do header . Este nome é um pouco infeliz, porque std::move simplesmente lança um lvalue para um rvalue; não move nada por si só. Apenas permite mover-se. Talvez devesse ter sido nomeado std::cast_to_rvalue ou std::enable_move , mas já estamos com o nome.

Aqui está como você se move explicitamente de um lvalue:

 unique_ptr a(new Triangle); unique_ptr b(a); // still an error unique_ptr c(std::move(a)); // okay 

Note que depois da terceira linha, não mais possui um triângulo. Tudo bem, porque ao escrever explicitamente std::move(a) , deixamos claras as nossas intenções: "Caro construtor, faça o que quiser com a fim de inicializar c ; não me importo mais com a . Sinta-se à vontade para ter seu caminho com a ".

std::move(some_lvalue) lança um lvalue para um rvalue, permitindo assim um movimento subseqüente.

Xvalores

Observe que, embora std::move(a) seja um rvalue, sua avaliação não cria um object temporário. Este enigma obrigou a comissão a introduzir uma terceira categoria de valor. Algo que pode ser vinculado a uma referência de valor, mesmo que não seja um rvalue no sentido tradicional, é chamado de xvalue (valor eXpirante). Os valores tradicionais foram renomeados para valores (Pure rvalues ).

Ambos os valores prvalues ​​e xvalues ​​são rvalues. Xvalues ​​e lvalues ​​são ambos glvalues (lvalues ​​generalizados). As relações são mais fáceis de entender com um diagrama:

  expressions / \ / \ / \ glvalues rvalues / \ / \ / \ / \ / \ / \ lvalues xvalues prvalues 

Note que apenas xvalues ​​são realmente novos; o resto é apenas devido à renomeação e agrupamento.

Os valores do C ++ 98 são conhecidos como prvalues ​​em C ++ 11. Mentalmente substitua todas as ocorrências de "rvalue" nos parágrafos anteriores por "prvalue".

Saindo de funções

Até agora, vimos o movimento em variables ​​locais e em parâmetros de function. Mas mover também é possível na direção oposta. Se uma function retornar por valor, algum object no site de chamada (provavelmente uma variável local ou temporária, mas poderia ser qualquer tipo de object) é inicializado com a expressão após a instrução de return como um argumento para o construtor de movimento:

 unique_ptr make_triangle() { return unique_ptr(new Triangle); } \-----------------------------/ | | temporary is moved into c | v unique_ptr c(make_triangle()); 

Talvez surpreendentemente, objects automáticos (variables ​​locais que não são declaradas como static ) também podem ser implicitamente removidos de funções:

 unique_ptr make_square() { unique_ptr result(new Square); return result; // note the missing std::move } 

Como o construtor de movimento aceita o result lvalue como um argumento? O escopo do result está prestes a terminar e será destruído durante o desenrolamento da pilha. Ninguém poderia reclamar depois que o result tivesse mudado de alguma forma; Quando o stream de controle está de volta ao chamador, o result não existe mais! Por esse motivo, o C ++ 11 possui uma regra especial que permite retornar objects automáticos de funções sem ter que escrever std::move . Na verdade, você nunca deve usar std::move para mover objects automáticos para fora de funções, pois isso inibe a "otimização de valor de retorno nomeado" (NRVO).

Nunca use std::move para mover objects automáticos fora das funções.

Observe que, em ambas as funções de fábrica, o tipo de retorno é um valor, não uma referência de valor. As referências de valor ainda são referências e, como sempre, você nunca deve retornar uma referência a um object automático; o chamador acabaria com uma referência pendente se você induzisse o compilador a aceitar seu código, assim:

 unique_ptr&& flawed_attempt() // DO NOT DO THIS! { unique_ptr very_bad_idea(new Square); return std::move(very_bad_idea); // WRONG! } 

Nunca retorne objects automáticos por referência de valor. Mover é executado exclusivamente pelo construtor de movimento, não por std::move , e não simplesmente pela vinculação de um rvalue a uma referência de rvalue.

Mover-se para membros

Mais cedo ou mais tarde, você vai escrever um código como este:

 class Foo { unique_ptr member; public: Foo(unique_ptr&& parameter) : member(parameter) // error {} }; 

Basicamente, o compilador irá reclamar que o parameter é um lvalue. Se você observar seu tipo, verá uma referência de valor, mas uma referência de valor simplesmente significa "uma referência que está vinculada a um valor"; isso não significa que a própria referência seja um valor! De fato, o parameter é apenas uma variável comum com um nome. Você pode usar o parameter quantas vezes quiser dentro do corpo do construtor e sempre denota o mesmo object. Implicitamente mover-se dele seria perigoso, daí a linguagem proíbe isso.

Uma referência de valor nominal é um lvalue, assim como qualquer outra variável.

A solução é ativar manualmente o movimento:

 class Foo { unique_ptr member; public: Foo(unique_ptr&& parameter) : member(std::move(parameter)) // note the std::move {} }; 

Você poderia argumentar que o parameter não é mais usado após a boot do member . Por que não há regra especial para inserir silenciosamente o std::move como nos valores de retorno? Provavelmente porque seria muito trabalho para os implementadores do compilador. Por exemplo, e se o corpo do construtor estivesse em outra unidade de tradução? Por outro lado, a regra de valor de retorno simplesmente precisa verificar as tabelas de símbolos para determinar se o identificador após a palavra-chave return indica um object automático ou não.

Você também pode passar o parameter por valor. Para os tipos somente de movimento, como unique_ptr , parece que ainda não existe uma expressão estabelecida. Pessoalmente, eu prefiro passar por valor, pois causa menos confusão na interface.

Funções especiais dos membros

O C ++ 98 declara implicitamente três funções de membro especiais sob demanda, ou seja, quando elas são necessárias em algum lugar: o construtor de cópia, o operador de atribuição de cópia e o destruidor.

 X::X(const X&); // copy constructor X& X::operator=(const X&); // copy assignment operator X::~X(); // destructor 

Referências de valor passaram por várias versões. Desde a versão 3.0, o C ++ 11 declara duas funções de membro especiais adicionais sob demanda: o construtor de movimento e o operador de designação de movimento. Observe que nem o VC10 nem o VC11 estão em conformidade com a versão 3.0, portanto, você precisará implementá-los por conta própria.

 X::X(X&&); // move constructor X& X::operator=(X&&); // move assignment operator 

Essas duas novas funções de membro especiais são declaradas apenas implicitamente se nenhuma das funções de membro especiais for declarada manualmente. Além disso, se você declarar seu próprio construtor de movimentação ou operador de atribuição de movimentação, nem o construtor de cópia nem o operador de atribuição de cópia serão declarados implicitamente.

O que essas regras significam na prática?

Se você escrever uma class sem resources não gerenciados, não será necessário declarar nenhuma das cinco funções especiais de membro, e obterá a semântica de cópia correta e moverá a semântica gratuitamente. Caso contrário, você terá que implementar as funções de membro especiais por conta própria. Obviamente, se sua class não se beneficia da semântica de movimento, não há necessidade de implementar as operações de movimentação especial.

Observe que o operador de atribuição de cópia e o operador de designação de movimento podem ser fundidos em um único operador de atribuição unificado, considerando seu argumento por valor:

 X& X::operator=(X source) // unified assignment operator { swap(source); // see my first answer for an explanation return *this; } 

Desta forma, o número de funções de membros especiais para implementar cai de cinco para quatro. Há uma compensação entre segurança e eficiência de exceção aqui, mas não sou especialista nessa questão.

Referências de encaminhamento ( anteriormente conhecidas como referências universais )

Considere o seguinte modelo de function:

 template void foo(T&&); 

Você pode esperar que T&& apenas se vincule a rvalues, porque, à primeira vista, parece uma referência rvalue. Como se vê, T&& também se liga a lvalues:

 foo(make_triangle()); // T is unique_ptr, T&& is unique_ptr&& unique_ptr a(new Triangle); foo(a); // T is unique_ptr&, T&& is unique_ptr& 

Se o argumento é um valor do tipo X , T é deduzido de ser X , portanto, T&& significa X&& . Isso é o que qualquer um esperaria. Mas se o argumento é um lvalue do tipo X , devido a uma regra especial, deduz-se que T é X& , portanto, T&& significaria algo como X& && . Mas, como o C ++ ainda não tem noção de referências a referências, o tipo X& && é recolhido no X& . Isso pode parecer confuso e inútil a princípio, mas o colapso de referência é essencial para o encaminhamento perfeito (que não será discutido aqui).

T && não é uma referência de valor, mas uma referência de encaminhamento. Ele também se liga a lvalores, caso em que T e T&& são duas referências lvalue.

Se você quiser restringir um modelo de function para valores de r, poderá combinar o SFINAE com características de tipo:

 #include  template typename std::enable_if::value, void>::type foo(T&&); 

Implementação de movimento

Agora que você entende o recolhimento de referência, std::move como o std::move é implementado:

 template typename std::remove_reference::type&& move(T&& t) { return static_cast::type&&>(t); } 

Como você pode ver, move aceita qualquer tipo de parâmetro graças à referência de encaminhamento T&& , e retorna uma referência de valor. A std::remove_reference::type meta-function é necessária porque caso contrário, para lvalues ​​do tipo X , o tipo de retorno seria X& && , que entraria em colapso em X& . Já que t é sempre um lvalue (lembre-se que uma referência rvalue nomeada é um lvalue), mas nós queremos ligar t a uma referência rvalue, nós temos que explicitamente converter t para o tipo de retorno correto. A chamada de uma function que retorna uma referência rvalue é ela própria um xvalue. Agora você sabe de onde os xvalues ​​vêm;)

A chamada de uma function que retorna uma referência rvalue, como std::move , é um xvalue.

Observe que retornar por referência rvalue é bom neste exemplo, porque t não denota um object automático, mas sim um object que foi passado pelo chamador.

As semânticas de movimento são baseadas em referências de valor .
Um rvalue é um object temporário, que será destruído no final da expressão. No C ++ atual, os valores apenas se ligam a referências const . C ++ 1x permitirá referências não const , especificadas como T&& , que são referências a objects rvalue.
Como um valor vai morrer no final de uma expressão, você pode roubar seus dados . Em vez de copiá- lo em outro object, você move seus dados para ele.

 class X { public: X(X&& rhs) // ctor taking an rvalue reference, so-called move-ctor : data_() { // since 'x' is an rvalue object, we can steal its data this->swap(std::move(rhs)); // this will leave rhs with the empty data } void swap(X&& rhs); // ... }; // ... X f(); X x = f(); // f() returns result as rvalue, so this calls move-ctor 

No código acima, com compiladores antigos, o resultado de f() é copiado em x usando o construtor de cópias de X Se o seu compilador suportar mover semântica e X tiver um construtor de movimento, então isso é chamado. Como seu argumento é um valor, sabemos que não é mais necessário e podemos roubar seu valor.
Assim, o valor é movido do temporário sem nome retornado de f() para x (enquanto os dados de x , inicializados para um X vazio, são movidos para o temporário, que será destruído após a atribuição).

Suponha que você tenha uma function que retorne um object substancial:

 Matrix multiply(const Matrix &a, const Matrix &b); 

Quando você escreve um código como este:

 Matrix r = multiply(a, b); 

então um compilador C ++ comum criará um object temporário para o resultado de multiply() , chamará o construtor de cópia para inicializar r e, em seguida, destruirá o valor de retorno temporário. Mover a semântica em C ++ 0x permite que o “construtor de movimento” seja chamado para inicializar r copiando seu conteúdo e, em seguida, descartar o valor temporário sem precisar destruí-lo.

Isso é especialmente importante se (como talvez o exemplo Matrix acima), o object que está sendo copiado aloca memory extra no heap para armazenar sua representação interna. Um construtor de cópia teria que fazer uma cópia completa da representação interna ou usar a semântica de contagem de referência e de cópia na escrita interativamente. Um construtor de movimento deixaria a memory heap sozinha e copiaria o ponteiro dentro do object Matrix .

If you are really interested in a good, in-depth explanation of move semantics, I’d highly recommend reading the original paper on them, “A Proposal to Add Move Semantics Support to the C++ Language.”

It’s very accessible and easy to read and it makes an excellent case for the benefits that they offer. There are other more recent and up to date papers about move semantics available on the WG21 website , but this one is probably the most straightforward since it approaches things from a top-level view and doesn’t get very much into the gritty language details.

Move semantics is about transferring resources rather than copying them when nobody needs the source value anymore.

In C++03, objects are often copied, only to be destroyed or assigned-over before any code uses the value again. For example, when you return by value from a function—unless RVO kicks in—the value you’re returning is copied to the caller’s stack frame, and then it goes out of scope and is destroyed. This is just one of many examples: see pass-by-value when the source object is a temporary, algorithms like sort that just rearrange items, reallocation in vector when its capacity() is exceeded, etc.

When such copy/destroy pairs are expensive, it’s typically because the object owns some heavyweight resource. For example, vector may own a dynamically-allocated memory block containing an array of string objects, each with its own dynamic memory. Copying such an object is costly: you have to allocate new memory for each dynamically-allocated blocks in the source, and copy all the values across. Then you need deallocate all that memory you just copied. However, moving a large vector means just copying a few pointers (that refer to the dynamic memory block) to the destination and zeroing them out in the source.

In easy (practical) terms:

Copying an object means copying its “static” members and calling the new operator for its dynamic objects. Certo?

 class A { int i, *p; public: A(const A& a) : i(ai), p(new int(*ap)) {} ~A() { delete p; } }; 

However, to move an object (I repeat, in a practical point of view) implies only to copy the pointers of dynamic objects, and not to create new ones.

But, is that not dangerous? Of course, you could destruct a dynamic object twice (segmentation fault). So, to avoid that, you should “invalidate” the source pointers to avoid destructing them twice:

 class A { int i, *p; public: // Movement of an object inside a copy constructor. A(const A& a) : i(ai), p(ap) { ap = nullptr; // pointer invalidated. } ~A() { delete p; } // Deleting NULL, 0 or nullptr (address 0x0) is safe. }; 

Ok, but if I move an object, the source object becomes useless, no? Of course, but in certain situations that’s very useful. The most evident one is when I call a function with an anonymous object (temporal, rvalue object, …, you can call it with different names):

 void heavyFunction(HeavyType()); 

In that situation, an anonymous object is created, next copied to the function parameter, and afterwards deleted. So, here it is better to move the object, because you don’t need the anonymous object and you can save time and memory.

This leads to the concept of an “rvalue” reference. They exist in C++11 only to detect if the received object is anonymous or not. I think you do already know that an “lvalue” is an assignable entity (the left part of the = operator), so you need a named reference to an object to be capable to act as an lvalue. A rvalue is exactly the opposite, an object with no named references. Because of that, anonymous object and rvalue are synonyms. Assim:

 class A { int i, *p; public: // Copy A(const A& a) : i(ai), p(new int(*ap)) {} // Movement (&& means "rvalue reference to") A(A&& a) : i(ai), p(ap) { ap = nullptr; } ~A() { delete p; } }; 

In this case, when an object of type A should be “copied”, the compiler creates a lvalue reference or a rvalue reference according to if the passed object is named or not. When not, your move-constructor is called and you know the object is temporal and you can move its dynamic objects instead of copying them, saving space and memory.

It is important to remember that “static” objects are always copied. There’s no ways to “move” a static object (object in stack and not on heap). So, the distinction “move”/ “copy” when an object has no dynamic members (directly or indirectly) is irrelevant.

If your object is complex and the destructor has other secondary effects, like calling to a library’s function, calling to other global functions or whatever it is, perhaps is better to signal a movement with a flag:

 class Heavy { bool b_moved; // staff public: A(const A& a) { /* definition */ } A(A&& a) : // initialization list { a.b_moved = true; } ~A() { if (!b_moved) /* destruct object */ } }; 

So, your code is shorter (you don’t need to do a nullptr assignment for each dynamic member) and more general.

Other typical question: what is the difference between A&& and const A&& ? Of course, in the first case, you can modify the object and in the second not, but, practical meaning? In the second case, you can’t modify it, so you have no ways to invalidate the object (except with a mutable flag or something like that), and there is no practical difference to a copy constructor.

And what is perfect forwarding ? It is important to know that a “rvalue reference” is a reference to a named object in the “caller’s scope”. But in the actual scope, a rvalue reference is a name to an object, so, it acts as a named object. If you pass an rvalue reference to another function, you are passing a named object, so, the object isn’t received like a temporal object.

 void some_function(A&& a) { other_function(a); } 

The object a would be copied to the actual parameter of other_function . If you want the object a continues being treated as a temporary object, you should use the std::move function:

 other_function(std::move(a)); 

With this line, std::move will cast a to an rvalue and other_function will receive the object as a unnamed object. Of course, if other_function has not specific overloading to work with unnamed objects, this distinction is not important.

Is that perfect forwarding? Not, but we are very close. Perfect forwarding is only useful to work with templates, with the purpose to say: if I need to pass an object to another function, I need that if I receive a named object, the object is passed as a named object, and when not, I want to pass it like a unnamed object:

 template void some_function(T&& a) { other_function(std::forward(a)); } 

That’s the signature of a prototypical function that uses perfect forwarding, implemented in C++11 by means of std::forward . This function exploits some rules of template instantiation:

  `A& && == A&` `A&& && == A&&` 

So, if T is a lvalue reference to A ( T = A&), a also ( A& && => A&). If T is a rvalue reference to A , a also (A&& && => A&&). In both cases, a is a named object in the actual scope, but T contains the information of its “reference type” from the caller scope’s point of view. This information ( T ) is passed as template parameter to forward and ‘a’ is moved or not according to the type of T .

It’s like copy semantics, but instead of having to duplicate all of the data you get to steal the data from the object being “moved” from.

You know what a copy semantics means right? it means you have types which are copyable, for user-defined types you define this either buy explicitly writing a copy constructor & assignment operator or the compiler generates them implicitly. This will do a copy.

Move semantics is basically a user-defined type with constructor that takes an r-value reference (new type of reference using && (yes two ampersands)) which is non-const, this is called a move constructor, same goes for assignment operator. So what does a move constructor do, well instead of copying memory from it’s source argument it ‘moves’ memory from the source to the destination.

When would you want to do that? well std::vector is an example, say you created a temporary std::vector and you return it from a function say:

 std::vector get_foos(); 

You’re going to have overhead from the copy constructor when the function returns, if (and it will in C++0x) std::vector has a move constructor instead of copying it can just set it’s pointers and ‘move’ dynamically allocated memory to the new instance. It’s kind of like transfer-of-ownership semantics with std::auto_ptr.

To illustrate the need for move semantics , let’s consider this example without move semantics:

Here’s a function that takes an object of type T and returns an object of the same type T :

 T f(T o) { return o; } //^^^ new object constructed 

The above function uses call by value which means that when this function is called an object must be constructed to be used by the function.
Because the function also returns by value , another new object is constructed for the return value:

 T b = f(a); //^ new object constructed 

Two new objects have been constructed, one of which is a temporary object that’s only used for the duration of the function.

When the new object is created from the return value, the copy constructor is called to copy the contents of the temporary object to the new object b. After the function completes, the temporary object used in the function goes out of scope and is destroyed.


Now, let’s consider what a copy constructor does.

It must first initialize the object, then copy all the relevant data from the old object to the new one.
Depending on the class, maybe its a container with very much data, then that could represent much time and memory usage

 // Copy constructor T::T(T &old) { copy_data(m_a, old.m_a); copy_data(m_b, old.m_b); copy_data(m_c, old.m_c); } 

With move semantics it’s now possible to make most of this work less unpleasant by simply moving the data rather than copying.

 // Move constructor T::T(T &&old) noexcept { m_a = std::move(old.m_a); m_b = std::move(old.m_b); m_c = std::move(old.m_c); } 

Moving the data involves re-associating the data with the new object. And no copy takes place at all.

This is accomplished with an rvalue reference.
An rvalue reference works pretty much like an lvalue reference with one important difference:
an rvalue reference can be moved and an lvalue cannot.

From cppreference.com :

To make strong exception guarantee possible, user-defined move constructors should not throw exceptions. In fact, standard containers typically rely on std::move_if_noexcept to choose between move and copy when container elements need to be relocated. If both copy and move constructors are provided, overload resolution selects the move constructor if the argument is an rvalue (either a prvalue such as a nameless temporary or an xvalue such as the result of std::move), and selects the copy constructor if the argument is an lvalue (named object or a function/operator returning lvalue reference). If only the copy constructor is provided, all argument categories select it (as long as it takes a reference to const, since rvalues can bind to const references), which makes copying the fallback for moving, when moving is unavailable. In many situations, move constructors are optimized out even if they would produce observable side-effects, see copy elision. A constructor is called a ‘move constructor’ when it takes an rvalue reference as a parameter. It is not obligated to move anything, the class is not required to have a resource to be moved and a ‘move constructor’ may not be able to move a resource as in the allowable (but maybe not sensible) case where the parameter is a const rvalue reference (const T&&).

I’m writing this to make sure I understand it properly.

Move semantics were created to avoid the unnecessary copying of large objects. Bjarne Stroustrup in his book “The C++ Programming Language” uses two examples where unnecessary copying occurs by default: one, the swapping of two large objects, and two, the returning of a large object from a method.

Swapping two large objects usually involves copying the first object to a temporary object, copying the second object to the first object, and copying the temporary object to the second object. For a built-in type, this is very fast, but for large objects these three copies could take a large amount of time. A “move assignment” allows the programmer to override the default copy behavior and instead swap references to the objects, which means that there is no copying at all and the swap operation is much faster. The move assignment can be invoked by calling the std::move() method.

Returning an object from a method by default involves making a copy of the local object and its associated data in a location which is accessible to the caller (because the local object is not accessible to the caller and disappears when the method finishes). When a built-in type is being returned, this operation is very fast, but if a large object is being returned, this could take a long time. The move constructor allows the programmer to override this default behavior and instead “reuse” the heap data associated with the local object by pointing the object being returned to the caller to heap data associated with the local object. Thus no copying is required.

In languages which do not allow the creation of local objects (that is, objects on the stack) these types of problems do not occur as all objects are allocated on the heap and are always accessed by reference.