Da trilha de C++ Este conceito ainda não saiu como card do dia. ir para o card de hoje

C++ Memória Intermediário

Cópia e movimento

Em C++, quando um objeto é criado a partir de outro, o compilador decide entre copiar (duplicar recursos) ou mover (transferir recursos). A cópia é feita pelo construtor de cópia, que recebe const T&. O movimento é feito pelo construtor de movimento, que recebe T&& e pode "roubar" os recursos do objeto original, deixando-o em estado válido mas vazio.

A função std::move não move nada: ela apenas converte um lvalue em rvalue, permitindo que o construtor de movimento seja chamado. É um cast para T&&.

A regra dos 5 diz que se você define destrutor, construtor de cópia, atribuição de cópia, construtor de movimento ou atribuição de movimento, provavelmente precisa definir todos os cinco para gerenciar recursos corretamente.

Neste card, vamos criar uma classe que gerencia um buffer dinâmico e implementar cópia e movimento para ver a diferença na prática.

Pontos-chave

  • O construtor de cópia (T(const T&)) duplica os recursos do objeto original.
  • O construtor de movimento (T(T&&)) transfere a posse dos recursos, deixando o original vazio.
  • std::move apenas converte para rvalue; não move nada por si só.
  • A regra dos 5: se precisar de um dos cinco, provavelmente precisa dos cinco.
  • Objetos movidos ficam em estado válido, mas não especificado; não os use após mover.
copia_movimento.cpp
#include <iostream>
#include <utility>
#include <cstring>

class Buffer {
    char* data_;
    size_t size_;
public:
    // Construtor comum
    explicit Buffer(size_t size) : data_(new char[size]), size_(size) {
        std::memset(data_, '\0', size_);
        std::cout << "Construtor: " << size_ << " bytes\n";
    }

    // Construtor de cópia: duplica os dados
    Buffer(const Buffer& other) : data_(new char[other.size_]), size_(other.size_) {
        std::memcpy(data_, other.data_, size_);
        std::cout << "Cópia: " << size_ << " bytes\n";
    }

    // Construtor de movimento: rouba os dados
    Buffer(Buffer&& other) noexcept : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
        std::cout << "Movimento: " << size_ << " bytes\n";
    }

    // Destrutor
    ~Buffer() {
        delete[] data_;
        std::cout << "Destrutor: " << size_ << " bytes\n";
    }

    // Atribuição de cópia
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data_;
            size_ = other.size_;
            data_ = new char[size_];
            std::memcpy(data_, other.data_, size_);
        }
        std::cout << "Atribuição de cópia\n";
        return *this;
    }

    // Atribuição de movimento
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        std::cout << "Atribuição de movimento\n";
        return *this;
    }
};

Buffer criarBuffer(size_t n) {
    return Buffer(n); // RVO / movimento
}

int main() {
    Buffer a(10);
    Buffer b = a;            // cópia
    Buffer c = std::move(a); // movimento
    Buffer d(20);
    d = criarBuffer(30);     // movimento por atribuição
    return 0;
}

Exercícios

  1. 1
    Fácil

    Implemente o construtor de cópia

    Complete a classe String para que o construtor de cópia faça uma cópia profunda da string alocada dinamicamente. O programa deve imprimir o conteúdo das duas strings e não deve causar double free.

    Resolver
  2. 2
    Médio

    Adicione construtor de movimento

    Adicione um construtor de movimento à classe String do exercício anterior. O movimento deve transferir a posse do buffer, deixando o objeto original com data_ = nullptr. O programa deve imprimir as strings e verificar que a string movida está vazia.

    Resolver
  3. 3
    Desafio

    Implemente a regra dos 5 completa

    Implemente a regra dos 5 completa para a classe String: construtor comum, construtor de cópia, atribuição de cópia, construtor de movimento, atribuição de movimento e destrutor. Teste a auto-atribuição e a movimentação. O programa deve imprimir mensagens indicando qual operação foi chamada.

    Resolver

Continue estudando