Menu
Coddy logo textTech

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á vazio

Marque 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.

challenge icon

Desafio

Fácil

Vamos 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 classe DataPacket que gerencia um payload de dados inteiros. Sua classe deve ter:
    • Membros privados: um ponteiro para um array de inteiros (payload), um size para o número de elementos e um packetId (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 noexcept que 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)
  • main.cpp: Demonstre a construção de movimento lendo um ID de pacote e um tamanho da entrada. Então:
    • Crie um DataPacket chamado original com os valores de entrada
    • Imprima "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • Crie um novo pacote chamado transferred movendo de original usando std::move()
    • Imprima "After move:"
    • Imprima "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • Imprima "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

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á vazio

Marque 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;
}
quiz iconTeste seus conhecimentos

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