Construtor de Movimentação
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 21 de 104.
Um construtor de movimento transfere recursos de um objeto temporário em vez de copiá-los. Enquanto um construtor de cópia duplica dados, um construtor de movimento "rouba" os recursos, deixando o objeto de origem em um estado válido, porém vazio. Isso evita cópias profundas dispendiosas quando o objeto de origem está prestes a ser destruído de qualquer forma.
O construtor de movimento recebe uma referência rvalue, denotada por &&:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Construtor de movimento
Buffer(Buffer&& other) noexcept {
data = other.data; // Rouba o ponteiro
size = other.size;
other.data = nullptr; // Deixa a origem em um estado válido
other.size = 0;
}
~Buffer() { delete[] data; }
};A principal diferença em relação à cópia: não alocamos nova memória. Nós simplesmente assumimos a propriedade da memória existente e definimos o ponteiro da origem como nullptr para que seu destrutor não delete nossos dados.
Construtores de movimento são chamados ao inicializar a partir de objetos temporários ou ao usar std::move():
Buffer createBuffer() {
return Buffer(1000); // Construtor de movimento usado (valor de retorno)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Movimentação explícita - b1 agora está vazioMarque os construtores de movimento como noexcept quando possível. Isso informa ao compilador que a operação não lançará exceções, permitindo otimizações importantes em contêineres como std::vector.
Desafio
FácilVamos construir um sistema de pacotes de dados que demonstra como os construtores de movimento transferem a propriedade de recursos de forma eficiente. Você criará uma classe DataPacket que gerencia um array de bytes alocado dinamicamente e implementará um construtor de movimento que "rouba" os dados em vez de copiá-los.
Você criará dois arquivos para organizar seu código:
DataPacket.h: Define uma classeDataPacketque gerencia um payload de dados inteiros. Sua classe deve ter:- Membros privados: um ponteiro para um array de inteiros (
payload), umsizepara o número de elementos e umpacketId(string) para identificar o pacote - Um construtor parametrizado que recebe um ID de pacote e um tamanho, aloca o array e o preenche com valores de 0 a size-1. Imprima
"Packet <id> created with size <size>" - Um construtor de movimento marcado como
noexceptque transfere a propriedade do payload do objeto de origem. Imprima"Packet <id> moved". Lembre-se de deixar a origem em um estado vazio válido (nullptr, size 0) - Um destruidor que libera a memória se o ponteiro não for nulo e imprime
"Packet <id> destroyed" - Um método
getSize()que retorna o tamanho atual - Um método
getId()que retorna o ID do pacote - Um método
getSum()que retorna a soma de todos os elementos no payload (retorne 0 se o payload for nulo)
- Membros privados: um ponteiro para um array de inteiros (
main.cpp: Demonstre a construção de movimento lendo um ID de pacote e um tamanho da entrada. Então:- Crie um
DataPacketchamadooriginalcom os valores de entrada - Imprima
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Crie um novo pacote chamado
transferredmovendo deoriginalusandostd::move() - Imprima
"After move:" - Imprima
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Imprima
"Transferred - ID: <id>, Size: <size>, Sum: <sum>"
- Crie um
O formato de entrada será:
- Primeira linha: ID do pacote (string)
- Segunda linha: tamanho (inteiro)
Após a movimentação, o pacote original deve ter tamanho 0 e soma 0 (já que seus dados foram transferidos), enquanto o pacote transferido deve ter todos os dados originais. Isso demonstra como a semântica de movimento evita cópias profundas caras, simplesmente transferindo a propriedade do ponteiro.
Inclua seu arquivo de cabeçalho no main.cpp usando #include "DataPacket.h" e não se esqueça de incluir <utility> para std::move().
Folha de consulta
Um construtor de movimento transfere recursos de um objeto temporário em vez de copiá-los, evitando cópias profundas dispendiosas. Ele recebe uma referência rvalue (&&) e "rouba" recursos, deixando o objeto de origem em um estado válido, porém vazio.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Construtor de movimento
Buffer(Buffer&& other) noexcept {
data = other.data; // Rouba o ponteiro
size = other.size;
other.data = nullptr; // Deixa a origem em um estado válido
other.size = 0;
}
~Buffer() { delete[] data; }
};Construtores de movimento são chamados ao inicializar a partir de objetos temporários ou ao usar std::move():
Buffer createBuffer() {
return Buffer(1000); // Construtor de movimento usado (valor de retorno)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Movimentação explícita - b1 agora está vazioMarque os construtores de movimento como noexcept quando possível para permitir otimizações em contêineres padrão.
Experimente você mesmo
#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"
using namespace std;
int main() {
// Ler entrada
string packetId;
int size;
cin >> packetId;
cin >> size;
// TODO: Criar um DataPacket chamado 'original' com os valores de entrada
// TODO: Imprimir "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Criar um novo pacote chamado 'transferred' movendo de original
// Dica: Use std::move()
// TODO: Imprimir "After move:"
// TODO: Imprimir "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Imprimir "Transferred - ID: <id>, Size: <size>, Sum: <sum>"
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico