Menu
Coddy logo textTech

Semântica de Movimento 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 (possui uma identidade persistente, pode ser endereçada) ou um rvalue (temporária, prestes a ser destruída). Compreender essa distinção desbloqueia a move semantics — 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 que você "roube" 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 para uma referência rvalue, sinalizando que você está disposto a abrir mão dos recursos do objeto. A movimentação real ocorre 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 construir uma classe DataBuffer gerenciadora de recursos que demonstra a semântica de movimento em ação. 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 rastrear 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 movimento que recebe uma referência rvalue, rouba os recursos e imprime: [name] moved from [source_name] (onde o buffer de destino assume o nome da origem)
    • Um destruidor que imprime [name] destroyed (ou empty destroyed se o buffer foi 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 movimento 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 em seu cabeçalho. Quando o destruidor for executado, delete os dados apenas se o ponteiro não for nulo. Inclua <iostream> para saída.

  • main.cpp: Leia duas entradas:
    1. Um nome para o seu buffer (string)
    2. Um tamanho para o seu buffer (inteiro)

    Crie um DataBuffer com o nome e tamanho fornecidos. Em seguida, crie um segundo buffer movendo do primeiro usando std::move(). Após o movimento, 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 movimento transfere a propriedade da memória alocada sem copiar nenhum dado. O buffer original é deixado em um estado vazio, porém válido, e quando ambos os buffers são destruídos ao final do programa, apenas aquele que ainda possui a memória realmente a deleta.

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