O que está por trás da palavra complicada "Semântica Move"?
\n\nImagine que você está se mudando para um apartamento novo. Você tem um sofá enorme e pesado. O que é mais rápido: carregá-lo nas costas por cinco lances de escada ou simplesmente entregar as chaves aos mudanças e dizer: "Pessoal, o sofá é de vocês, façam o que quiserem, eu não preciso mais dele"? A segunda opção é exatamente a semântica move em C++.
\n\nAntes do padrão C++11, os programadores sofriam copiando dados. Cada vez que você retornava um vetor ou uma string de uma função, o compilador criava uma cópia exata. Se o vetor tivesse um milhão de elementos, ele copiava cada um deles fielmente. Isso era lento, ineficiente e parecia copiar um livro à mão em vez de simplesmente entregar o original.
\n\nA semântica move é um mecanismo que permite mover recursos (memória, descritores de arquivo, sockets) de um objeto para outro sem a cópia custosa. Após a movimentação, o objeto original fica em um estado "vazio", mas válido, podendo ser destruído com segurança ou receber um novo valor.
\n\nNeste artigo, vamos explorar rvalue references, a função std::move, construtores de movimentação e operadores de atribuição — tudo explicado de forma simples e com exemplos de código.
\n\nPor que o código fica lento sem a semântica move?
\n\nVamos ver um exemplo clássico que atormentava todos os programadores C++ antes de 2011:
\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nstd::vector<std::string> criarVetorGrande() {\n std::vector<std::string> vec(1000000, "Olá");\n return vec; // Horror! Copiando um milhão de strings!\n}\n\nint main() {\n std::vector<std::string> dados = criarVetorGrande();\n std::cout << "Tamanho: " << dados.size() << std::endl;\n return 0;\n}\n\nO que acontece aqui? A função criarVetorGrande cria um vetor, preenche com dados e o retorna. O compilador (sem otimizações) faz o seguinte:
- \n
- Cria o vetor dentro da função. \n
- Copia o vetor INTEIRO para o valor de retorno. \n
- Destrói o vetor original. \n
- Em
main, copia esse valor para a variáveldados. \n
Resultado: duas cópias completas de um milhão de strings. Isso não é apenas lento — é um desastre de desempenho.
\n\nRvalue e Lvalue: os dois mundos do C++
\n\nPara entender a semântica move, é preciso conhecer dois conceitos fundamentais: lvalue e rvalue.
\n\n- \n
- Lvalue (left value) — um objeto que tem um endereço na memória e um nome. Você pode pegar o endereço dele (
&obj). Exemplo: a variávelint x = 5;—xé um lvalue. \n - Rvalue (right value) — um valor temporário que não tem nome e não pode ficar à esquerda do operador de atribuição. Exemplo:
5,x + 3, o resultado da funçãogetTemp(). Você não pode escrever&(x + 3). \n
Antes, rvalue references (representadas por &&) não existiam. O compilador via um objeto temporário e dizia: "Ah, é um rvalue, a única coisa que posso fazer é copiá-lo ou destruí-lo". A semântica move deu ao compilador uma terceira opção: roubar os recursos do rvalue, já que ele vai morrer de qualquer jeito.
Como funcionam std::move e as rvalue references?
\n\nA função std::move é apenas uma conversão de tipo. Ela não move dados fisicamente, apenas transforma um lvalue em rvalue, dando ao compilador permissão para "saquear" o objeto.
\n\nVamos analisar uma classe simples que gerencia memória dinâmica:
\n\n#include <iostream>\n#include <cstring>\n\nclass MinhaString {\nprivate:\n cha