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
nullptrsem 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
- O construtor de movimento recebe
String&& other, uma referência rvalue. - Inicializa
data_comother.data_, transferindo o ponteiro. - Define
other.data_ = nullptrpara que o destrutor do objeto movido não libere a memória transferida. - O destrutor verifica
delete[] nullptr, que é seguro. std::move(a)converteapara rvalue, permitindo que o construtor de movimento seja escolhido.- A saída mostra que
bcontém "world" eaestá 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 fornullptr, o destrutor libera a memória que agora pertence ab, causando double free. - Não marcar como
noexcept:std::vectorpode 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: