C++: Semântica de Movimentação em Profundidade

Última atualização: 2026-08-26

Na Lição 44, terminamos de aprender sobre sincronização multi-thread.

Agora, vamos mergulhar em um dos recursos mais importantes do C++11 — semântica de movimentação.

Compreender a semântica de movimentação é a chave para verdadeiramente entender a otimização de desempenho do C++ moderno.


1. Visão Geral da Semântica de Movimentação

(1) 1.1 Por que Precisamos de Semântica de Movimentação?

Problema: No C++98/03, copiar objetos temporários (rvalues) é desperdício.

Exemplo: Copiando Objetos Temporários (Dificuldade ⭐)

▶ Exemplo 2: Uso de Contêiner STL (Dificuldade ⭐)

TEXT 📖 Somente leitura
std::vector<int> createVector() {
 std::vector<int> v = {1, 2, 3, 4, 5};
 return v; // Return temporary object
}

std::vector<int> dest = createVector(); // Copy the temporary object, wasteful!

Saída:

TEXT 📖 Somente leitura
Copy count: 0

Solução da semântica de movimentação: "Roubar" os recursos do objeto temporário em vez de copiá-los.


(2) 1.2 Lvalue vs Rvalue

Categoria Descrição Exemplo
Lvalue Tem nome, pode-se obter o endereço x em int x = 10;
Rvalue Objeto temporário, prestes a ser destruído 10, x + 1, valores de retorno de funções

Analogia do mundo real:



2. Referências Rvalue

(1) 2.1 Sintaxe Básica

Referências rvalue usam && e só podem se vincular a rvalues.

Exemplo: Referências Rvalue (Dificuldade ⭐)

CPP
#include <iostream>

void process(int& x) {
 std::cout << "Processing lvalue: " << x << std::endl;
}

void process(int&& x) {
 std::cout << "Processing rvalue: " << x << std::endl;
}

int main() {
 int a = 10;
 process(a); // Calls process(int&)
 process(20); // Calls process(int&&)
 
 return 0;
}

(2) 2.2 std::move

std::move é usado para converter um lvalue em uma referência rvalue, sinalizando "eu não preciso mais deste objeto, você pode roubar seus recursos."

Exemplo: Usando std::move para Transferir Recursos (Dificuldade ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <utility>

int main() {
 std::vector<int> v1 = {1, 2, 3};
 std::vector<int> v2 = std::move(v1); // Move construction: v1's resources are "stolen" by v2
 
 std::cout << "v2 size: " << v2.size() << std::endl; // 3
 std::cout << "v1 size: " << v1.size() << std::endl; // 0 (v1 is empty after being moved from)
 
 return 0;
}

💡 Dica:



3. Construtor de Movimentação e Atribuição por Movimentação

(1) 3.1 Por que Precisamos de Operações de Movimentação Personalizadas?

Problema: Operações de movimentação geradas pelo compilador podem não ser eficientes (por exemplo, para classes com cópia profunda).

Solução: Defina um construtor de movimentação e operador de atribuição por movimentação personalizados.


(2) 3.2 Exemplo: Implementando Operações de Movimentação (Dificuldade ⭐⭐⭐)

CPP
#include <iostream>
#include <cstring>

class String {
private:
 char* data;
 size_t length;

public:
 // Constructor
 String(const char* str) {
 length = strlen(str);
 data = new char[length + 1];
 strcpy(data, str);
 std::cout << "Construct:" << data << std::endl;
 }
 
 // Copy constructor (deep copy)
 String(const String& other) {
 length = other.length;
 data = new char[length + 1];
 strcpy(data, other.data);
 std::cout << "Copy construction: " << data << std::endl;
 }
 
 // Move constructor (steal resources)
 String(String&& other) noexcept {
 data = other.data; // Steal the pointer
 length = other.length;
 other.data = nullptr; // Set to null, prevent double free
 other.length = 0;
 std::cout << "Move construction" << std::endl;
 }
 
 // Destructor
 ~String() {
 delete data;
 }
};

int main() {
 String s1("Hello");
 String s2 = std::move(s1); // Calls the move constructor
 
 return 0;
}

Saída:

TEXT 📖 Somente leitura
Construct:Hello
Move construction

Resultado da Execução:

TEXT 📖 Somente leitura
Construct:Hello
Move construction


4. Regras da Semântica de Movimentação

(1) 4.1 Os Cinco Grandes

Se uma classe precisa de um destrutor, construtor de cópia ou operador de atribuição por cópia personalizado, tipicamente também precisa de construtor de movimentação e operador de atribuição por movimentação personalizados.

Função Descrição
Destrutor Liberar recursos
Construtor de cópia Cópia profunda
Operador de atribuição por cópia Atribuição por cópia profunda
Construtor de movimentação Roubar recursos
Operador de atribuição por movimentação Atribuição roubando recursos

(2) 4.2 Regra do Zero

Melhor Prática: Se você usa smart pointers para gerenciar recursos, não precisa definir nenhum dos Cinco Grandes (o compilador gerará automaticamente versões corretas).

TEXT 📖 Somente leitura
class Person {
 std::string name; // Using string, automatically supports move
 std::vector<int> scores; // Using vector, automatically supports move
 // No need to define the Big Five!
}


5. Encaminhamento Perfeito

(1) 5.1 O que é Encaminhamento Perfeito?

Encaminhamento perfeito significa passar argumentos para outras funções sem alterar sua natureza de lvalue/rvalue.

Exemplo: Usando std::forward para Encaminhamento Perfeito (Dificuldade ⭐⭐⭐)

CPP
#include <iostream>
#include <utility>

void process(int& x) {
 std::cout << "Processing lvalue" << std::endl;
}

void process(int&& x) {
 std::cout << "Processing rvalue" << std::endl;
}

template<typename T>
void wrapper(T&& arg) {
 process(std::forward<T>(arg)); // Perfect forwarding
}

int main() {
 int x = 10;
 wrapper(x); // Forwards as lvalue
 wrapper(20); // Forwards as rvalue
 
 return 0;
}


6. Vantagens de Desempenho da Semântica de Movimentação

▶ Exemplo 1: Comparando Cópia e Movimentação (Dificuldade ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <chrono>

int main() {
 std::vector<std::vector<int>> v;
 
 // Test copy
 auto start = std::chrono::high_resolution_clock::now();
 for (int i = 0; i < 10000; i++) {
 std::vector<int> temp(1000, 1);
 v.push_back(temp); // Copy
 }
 auto end = std::chrono::high_resolution_clock::now();
 auto copy_time = std::chrono::duration<double>(end - start).count();
 
 v.clear();
 
 // Test move
 start = std::chrono::high_resolution_clock::now();
 for (int i = 0; i < 10000; i++) {
 std::vector<int> temp(1000, 1);
 v.push_back(std::move(temp)); // Move
 }
 end = std::chrono::high_resolution_clock::now();
 auto move_time = std::chrono::duration<double>(end - start).count();
 
 std::cout << "Copy time: " << copy_time << " seconds" << std::endl;
 std::cout << "Move time: " << move_time << " seconds" << std::endl;
 
 return 0;
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Copy time: 0.5 seconds
Move time: 0.01 seconds

▶ Exemplo 3: std::move Transfere a Propriedade de Recursos (Dificuldade ⭐)

CPP
#include <iostream>
#include <vector>
#include <string>

int main() {
    std::string original = "Hello, C++!";
    std::string moved = std::move(original);

    std::cout << "moved: " << moved << std::endl;
    std::cout << "original after move: \"" << original << "\"" << std::endl;

    return 0;
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
moved: Hello, C++!
original after move: ""

❓ Perguntas Frequentes

P: std::move sempre realiza uma movimentação? R: Não necessariamente. Se não houver construtor de movimentação, o construtor de cópia será chamado em vez disso (fallback).


P: Em que estado fica um objeto após ser movido? R: Um objeto movido fica em um estado válido mas não especificado (tipicamente vazio). Você pode atribuir um novo valor a ele, mas não pode assumir seu valor.


P: Quando devo usar semântica de movimentação? R: - Objetos temporários (valores de retorno de funções) - Objetos que não são mais necessários (std::move) - Inserção de elementos em contêineres (emplace_back)


📖 Resumo

Conceito Ponto-chave
Referência rvalue &&, só pode se vincular a rvalues
std::move Converte um lvalue em uma referência rvalue
Construtor de movimentação Rouba recursos, sem cópia
Atribuição por movimentação Rouba recursos para atribuição
Encaminhamento perfeito std::forward, preserva a categoria de valor

📝 Exercícios

  1. Básico (Dificuldade ⭐): Crie um vector<string>, adicione várias strings usando push_back e observe a saída para confirmar que cópias estão acontecendo. Depois compare com emplace_back.

  2. Intermediário (Dificuldade ⭐⭐): Implemente uma classe MyString (com construtor de cópia e construtor de movimentação), acione a semântica de movimentação via std::move em main e observe qual construtor é chamado.

  3. Desafio (Dificuldade ⭐⭐⭐): Implemente um tipo somente-movível (como unique_ptr), desabilite o construtor de cópia e atribuição por cópia, habilite o construtor de movimentação e atribuição por movimentação. Escreva código de teste para verificar que não pode ser copiado mas pode ser movido.



Próxima Lição: Smart Pointers Avançado (#46)

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%