Sobrecarga do Operador de Atribuição
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 44 de 104.
O operador de atribuição por cópia (=) é chamado quando você atribui um objeto existente a outro. Ao contrário do construtor de cópia (que cria um novo objeto), o operador de atribuição trabalha com um objeto que já existe e pode ter recursos que precisam ser limpos primeiro.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; }
Buffer& operator=(const Buffer& other) {
if (this != &other) { // 1. Verificação de autoatribuição
delete[] data; // 2. Limpar recurso existente
size = other.size; // 3. Copiar os dados
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this; // 4. Retornar *this
}
};A verificação de autoatribuição (this != &other) é crítica. Sem ela, b = b; excluiria os dados antes de tentar copiá-los, causando comportamento indefinido.
O operador retorna *this por referência para permitir o encadeamento como a = b = c;. Isso corresponde ao comportamento dos tipos integrados.
Para classes que gerenciam recursos, você frequentemente implementará tanto operadores de atribuição por cópia quanto por movimento:
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // Limpa o recurso atual
data = other.data; // Rouba o recurso
size = other.size;
other.data = nullptr; // Deixa a origem em um estado válido
other.size = 0;
}
return *this;
}O operador de atribuição por movimento transfere a propriedade em vez de copiar, tornando operações como vec[0] = createBuffer(); muito mais eficientes.
Desafio
FácilVamos criar uma classe DynamicString que gerencia um array de caracteres alocado dinamicamente e lida corretamente com a atribuição entre objetos existentes. Este é um cenário prático em que você precisa transferir com segurança dados de string de um objeto para outro sem vazamentos de memória ou ponteiros pendentes.
Você criará dois arquivos para organizar seu código:
DynamicString.h: Defina uma classeDynamicStringque armazena uma string no estilo C usando memória dinâmica. Sua classe deve gerenciar:- Um ponteiro
char*privado para os dados da string - Um
size_tprivado para o comprimento - Um construtor que recebe um
const char*e cria uma cópia profunda - Um destrutor que libera corretamente a memória alocada
- Um operador de atribuição por cópia que realiza uma cópia profunda com proteção contra autoatribuição
- Um operador de atribuição por movimento que transfere a propriedade de forma eficiente
- Um método
c_str()que retorna a string interna (const) - Um método
length()que retorna o comprimento da string (const)
Seu operador de atribuição por cópia deve seguir as quatro etapas essenciais: verificar se há autoatribuição, limpar os recursos existentes, copiar os novos dados e retornar
*this. O operador de atribuição por movimento deve assumir o ponteiro da origem e deixar a origem em um estado vazio válido (nullptr com comprimento 0).- Um ponteiro
main.cpp: Leia três strings da entrada (cada uma em uma linha separada). Crie três objetosDynamicString:str1com a primeira entrada,str2com a segunda entrada estr3com a terceira entrada.Demonstre seus operadores de atribuição:
- Imprima o estado inicial das três strings
- Use a atribuição por cópia:
str1 = str2; - Imprima str1 e str2 após a atribuição por cópia (ambas devem ter o mesmo conteúdo)
- Use a atribuição por movimento:
str2 = std::move(str3); - Imprima str2 após a atribuição por movimento e o comprimento de str3 (deve ser 0 após a movimentação)
Formato da saída:
Initial: str1: <value> str2: <value> str3: <value> After copy (str1 = str2): str1: <value> str2: <value> After move (str2 = std::move(str3)): str2: <value> str3 length: 0
Use <cstring> para strlen e strcpy. Marque seu operador de atribuição por movimento como noexcept. Inclua <utility> em main.cpp para std::move. Não se esqueça dos protetores de inclusão no arquivo de cabeçalho.
Experimente você mesmo
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// Lê três strings da entrada
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// Cria três objetos DynamicString
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: Imprimir o estado inicial de todas as três strings
// Formato:
// Initial:
// str1: <valor>
// str2: <valor>
// str3: <valor>
// TODO: Usar atribuição por cópia: str1 = str2;
// TODO: Imprimir str1 e str2 após a atribuição por cópia
// Formato:
// After copy (str1 = str2):
// str1: <valor>
// str2: <valor>
// TODO: Usar atribuição por movimento: str2 = std::move(str3);
// TODO: Imprimir str2 após o movimento, e o comprimento de str3
// Formato:
// After move (str2 = std::move(str3)):
// str2: <valor>
// str3 length: 0
return 0;
}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
1Fundamentos de POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online