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ário, prestes a ser destruído). 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 maneira:

#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";
    }
    
    // Construtor de movimento - rouba recursos
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // 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));  // 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 contêineres 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, mas 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.

Folha de consulta

Expressões C++ são **lvalues** (persistentes, endereçáveis) ou **rvalues** (temporárias). A **semântica de movimento** otimiza o desempenho transferindo recursos de objetos temporários em vez de copiá-los.

Referências Rvalue

Declaradas com &&, referências rvalue se vinculam a objetos temporários:

Buffer(Buffer&& other)  // Parâmetro de referência rvalue

Construtor de Movimento

Rouba recursos de um objeto temporário. Marque-o como noexcept e deixe a origem em um estado vazio válido:

Buffer(Buffer&& other) noexcept 
    : data(other.data), size(other.size) {
    other.data = nullptr;  // Deixa a origem válida
    other.size = 0;
}

std::move

Converte um lvalue para uma referência rvalue, sinalizando a disposição para transferir recursos. Inclua <utility>:

Buffer b1(1000);
Buffer b2(std::move(b1));  // Aciona o construtor de movimento

std::move não move nada por si só - a transferência real acontece no construtor de movimento ou no operador de atribuição por movimento.

Benefícios

A semântica de movimento fornece transferência de recursos em tempo constante, independentemente do tamanho do objeto, evitando cópias profundas dispendiosas de grandes estruturas de dados.

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