Médio C++

Adicione construtor de movimento

Enunciado

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.

Requisitos

  • O construtor de movimento recebe String&& e é noexcept.
  • Após o movimento, o objeto original tem data_ == nullptr.
  • O destrutor lida com nullptr sem erro.

Código inicial

#include <iostream>
#include <cstring>
#include <utility>

class String {
    char* data_;
public:
    String(const char* s) {
        data_ = new char[std::strlen(s) + 1];
        std::strcpy(data_, s);
    }
    String(const String& other) {
        data_ = new char[std::strlen(other.data_) + 1];
        std::strcpy(data_, other.data_);
    }
    // TODO: construtor de movimento
    ~String() { delete[] data_; }
    const char* c_str() const { return data_ ? data_ : ""; }
};

int main() {
    String a("world");
    String b = std::move(a);
    std::cout << "b: " << b.c_str() << "\n";
    std::cout << "a: " << a.c_str() << "\n";
    return 0;
}

Saída esperada

b: world
a:
Ver dica

No construtor de movimento, copie o ponteiro e o tamanho, depois zere o original. Use noexcept.

Mostrar solução
#include <iostream>
#include <cstring>
#include <utility>

class String {
    char* data_;
public:
    String(const char* s) {
        data_ = new char[std::strlen(s) + 1];
        std::strcpy(data_, s);
    }

    // Construtor de cópia
    String(const String& other) {
        data_ = new char[std::strlen(other.data_) + 1];
        std::strcpy(data_, other.data_);
    }

    // Construtor de movimento: transfere a posse
    String(String&& other) noexcept : data_(other.data_) {
        other.data_ = nullptr;
    }

    ~String() { delete[] data_; }
    const char* c_str() const { return data_ ? data_ : ""; }
};

int main() {
    String a("world");
    String b = std::move(a);
    std::cout << "b: " << b.c_str() << "\n";
    std::cout << "a: " << a.c_str() << "\n";
    return 0;
}

Passo a passo

  1. O construtor de movimento recebe String&& other, uma referência rvalue.
  2. Inicializa data_ com other.data_, transferindo o ponteiro.
  3. Define other.data_ = nullptr para que o destrutor do objeto movido não libere a memória transferida.
  4. O destrutor verifica delete[] nullptr, que é seguro.
  5. std::move(a) converte a para rvalue, permitindo que o construtor de movimento seja escolhido.
  6. A saída mostra que b contém "world" e a está vazia.

Por que funciona

O movimento evita uma cópia profunda desnecessária: em vez de alocar e copiar, apenas transfere o ponteiro. Isso é essencial para desempenho. O noexcept é importante porque permite que std::vector use movimento ao realocar.

Erros comuns

  • Esquecer de zerar o original: se other.data_ não for nullptr, o destrutor libera a memória que agora pertence a b, causando double free.
  • Não marcar como noexcept: std::vector pode preferir cópia em vez de movimento.
  • Usar const String&&: impede a transferência, pois não se pode modificar o original.

Outra forma de resolver

Usar std::exchange para transferir e zerar em uma linha:

String(String&& other) noexcept : data_(std::exchange(other.data_, nullptr)) {}

Saída esperada

b: world
a: