Desafio C++
Implemente a regra dos 5 completa
Enunciado
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.
Requisitos
- Todos os cinco membros especiais são definidos.
- A atribuição de cópia e movimento tratam auto-atribuição (
this == &other). - O destrutor libera memória e lida com
nullptr. - A saída mostra a sequência de operações.
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);
std::cout << "Construtor\n";
}
// TODO: construtor de cópia, atribuição de cópia,
// construtor de movimento, atribuição de movimento, destrutor
const char* c_str() const { return data_ ? data_ : ""; }
};
int main() {
String a("alpha");
String b = a; // cópia
String c = std::move(a); // movimento
b = c; // atribuição de cópia
c = std::move(b); // atribuição de movimento
b = b; // auto-atribuição
std::cout << "c: " << c.c_str() << "\n";
return 0;
}
Saída esperada
Construtor
Cópia
Movimento
Atribuição de cópia
Atribuição de movimento
Auto-atribuição
c: alpha
Ver dica
Na atribuição, verifique if (this != &other) antes de liberar recursos. Use std::exchange para simplificar.
Mostrar solução
#include <iostream>
#include <cstring>
#include <utility>
class String {
char* data_;
public:
// Construtor comum
String(const char* s) {
data_ = new char[std::strlen(s) + 1];
std::strcpy(data_, s);
std::cout << "Construtor\n";
}
// Construtor de cópia
String(const String& other) {
data_ = new char[std::strlen(other.data_) + 1];
std::strcpy(data_, other.data_);
std::cout << "Cópia\n";
}
// Atribuição de cópia
String& operator=(const String& other) {
if (this != &other) {
delete[] data_;
data_ = new char[std::strlen(other.data_) + 1];
std::strcpy(data_, other.data_);
}
std::cout << "Atribuição de cópia\n";
return *this;
}
// Construtor de movimento
String(String&& other) noexcept : data_(other.data_) {
other.data_ = nullptr;
std::cout << "Movimento\n";
}
// Atribuição de movimento
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
other.data_ = nullptr;
}
std::cout << "Atribuição de movimento\n";
return *this;
}
// Destrutor
~String() { delete[] data_; }
const char* c_str() const { return data_ ? data_ : ""; }
};
int main() {
String a("alpha");
String b = a; // cópia
String c = std::move(a); // movimento
b = c; // atribuição de cópia
c = std::move(b); // atribuição de movimento
b = b; // auto-atribuição
std::cout << "c: " << c.c_str() << "\n";
return 0;
}
Passo a passo
- O construtor comum aloca e copia a string, imprimindo "Construtor".
- O construtor de cópia faz cópia profunda e imprime "Cópia".
- A atribuição de cópia verifica auto-atribuição, libera o buffer antigo, aloca novo e copia. Imprime "Atribuição de cópia".
- O construtor de movimento transfere o ponteiro e zera o original, imprimindo "Movimento".
- A atribuição de movimento verifica auto-atribuição, libera o buffer antigo, transfere o ponteiro e zera o original. Imprime "Atribuição de movimento".
- O destrutor libera a memória.
- No
main, as operações são executadas na ordem, e a auto-atribuição é tratada sem liberar memória prematuramente.
Por que funciona
A regra dos 5 garante que todos os recursos sejam gerenciados corretamente. A verificação de auto-atribuição evita que delete[] data_ libere a memória antes de copiá-la. O movimento transfere a posse sem alocar, melhorando o desempenho.
Erros comuns
- Esquecer a auto-atribuição:
b = b;liberadata_e depois tenta copiar de si mesmo, causando use-after-free. - Não liberar memória na atribuição: vazamento de memória.
- Não zerar o original no movimento: double free.
- Não marcar movimento como
noexcept:std::vectorpode não usar movimento.
Outra forma de resolver
Usar a copy-and-swap idiom para simplificar as atribuições:
String& operator=(String other) {
std::swap(data_, other.data_);
return *this;
}
Saída esperada
Construtor
Cópia
Movimento
Atribuição de cópia
Atribuição de movimento
Auto-atribuição
c: alpha