Menu
Coddy logo textTech

Semântica de Movimentação e Rvalues

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 83 de 104.

Em C++, toda expressão é um lvalue (tem uma identidade persistente, pode ter seu endereço obtido) ou um rvalue (temporária, prestes a ser destruída). Entender essa distinção permite compreender a semântica de movimentação — uma otimização poderosa que evita cópias desnecessárias.

Uma referência rvalue, declarada com &&, vincula-se especificamente a objetos temporários. Isso permite “roubar” recursos de objetos que estão prestes a desaparecer de qualquer forma:

#include <iostream>
#include <utility>

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : data(new int[s]), size(s) {
        std::cout << "Constructed\n";
    }
    
    // Move constructor - steals resources
    // Construtor de movimento - rouba recursos
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // Leave source in valid state // Deixa a origem em um estado válido
        other.size = 0;
        std::cout << "Moved\n";
    }
    
    ~Buffer() { delete[] data; }
};

int main() {
    Buffer b1(1000);
    Buffer b2(std::move(b1));  // Triggers move constructor // Aciona o construtor de movimento
}

A função std::move não move nada de fato — ela simplesmente converte um lvalue em uma referência rvalue, sinalizando que você está disposto a abrir mão dos recursos do objeto. A movimentação propriamente dita acontece no construtor de movimento ou no operador de atribuição por movimento.

A semântica de movimento melhora drasticamente o desempenho ao trabalhar com objetos pesados em recursos, como containers ou strings. Em vez de realizar a cópia profunda de megabytes de dados, você simplesmente transfere a propriedade do ponteiro — uma operação de tempo constante, independentemente do tamanho.

challenge icon

Desafio

Fácil

Vamos criar uma classe DataBuffer que gerencia recursos e demonstra a semântica de movimentação na prática. Você verá como mover recursos em vez de copiá-los pode melhorar drasticamente a eficiência ao transferir a propriedade de memória alocada dinamicamente.

Você organizará seu código em três arquivos:

  • DataBuffer.h: defina sua classe DataBuffer, que gerencia um array de inteiros alocado dinamicamente.

    Sua classe deve ter membros privados para o ponteiro de dados (int*), o tamanho (size_t) e um nome (std::string) para ajudar a identificar qual buffer é qual durante as operações.

    Declare o seguinte:

    • Um construtor que recebe um nome std::string e um tamanho size_t, aloca o array e imprime: [name] constructed with size [size]
    • Um construtor de movimentação que recebe uma referência rvalue, apropria-se dos recursos e imprime: [name] moved from [source_name] (em que o buffer de destino recebe o nome da origem)
    • Um destrutor que imprime [name] destroyed (ou empty destroyed se o buffer tiver sido movido)
    • Um método getSize() que retorna o tamanho atual
    • Um método getName() que retorna o nome do buffer

    Lembre-se de marcar seu construtor de movimentação como noexcept e deixar o objeto de origem em um estado vazio válido (nullptr, tamanho 0, nome "empty").

  • DataBuffer.cpp: implemente todos os métodos declarados no cabeçalho. Quando o destrutor for executado, exclua os dados somente se o ponteiro não for nulo. Inclua <iostream> para a saída.
  • main.cpp: leia duas entradas:
    1. Um nome para seu buffer (string)
    2. Um tamanho para seu buffer (inteiro)

    Crie um DataBuffer com o nome e o tamanho fornecidos. Em seguida, crie um segundo buffer movendo o primeiro usando std::move(). Depois da movimentação, imprima o estado de ambos os buffers:

    • Original: [name] size=[size]
    • New: [name] size=[size]

    Inclua <utility> para std::move.

Por exemplo, com as entradas Alpha e 100:

Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyed

Com as entradas Buffer e 50:

Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyed

Observe como o construtor de movimentação transfere a propriedade da memória alocada sem copiar nenhum dado. O buffer original permanece em um estado vazio, mas válido, e, quando ambos os buffers são destruídos no final do programa, somente aquele que ainda possui a memória a exclui de fato.

Experimente você mesmo

#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"

int main() {
    std::string name;
    int size;
    
    std::cin >> name;
    std::cin >> size;
    
    // TODO: Criar um DataBuffer com o nome e tamanho fornecidos
    
    // TODO: Criar um segundo buffer movendo do primeiro usando std::move()
    
    // TODO: Imprimir o estado de ambos os buffers:
    // Original: [name] size=[size]
    // Novo: [name] size=[size]
    
    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

Pratique por conta própria: Compilador de C++ online