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. Diferente 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 movimentação:
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 construir uma classe DynamicString que gerencia um array de caracteres alocado dinamicamente e lida adequadamente com a atribuição entre objetos existentes. Este é um cenário prático onde você precisa transferir dados de string de um objeto para outro com segurança, 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 (deep copy) - Um destrutor que libera adequadamente 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 os quatro passos essenciais: verificar a autoatribuição, limpar os recursos existentes, copiar os novos dados e retornar
*this. O operador de atribuição por movimento deve "roubar" 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 de todas as 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 ser movida)
Formato de 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 header guards em seu arquivo de cabeçalho.
Folha de consulta
O operador de atribuição por cópia (=) atribui um objeto existente a outro. Ele deve lidar com a limpeza dos recursos existentes antes de copiar os novos dados.
Estrutura do Operador de Atribuição por Cópia
ClassName& operator=(const ClassName& other) {
if (this != &other) { // 1. Self-assignment check
// 2. Clean up existing resources
// 3. Copy data from other
}
return *this; // 4. Return *this
}Exemplo com Memória Dinâmica
class Buffer {
int* data;
size_t size;
public:
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data; // Clean up existing resource
size = other.size;
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this;
}
};A verificação de autoatribuição (this != &other) evita a exclusão de dados antes de copiá-los em casos como b = b;.
Retornar *this por referência permite o encadeamento: a = b = c;.
Operador de Atribuição por Movimento
O operador de atribuição por movimento transfere a propriedade em vez de copiar, aumentando a eficiência:
ClassName& operator=(ClassName&& other) noexcept {
if (this != &other) {
// Clean up current resource
// Steal the resource from other
// Leave other in valid state (nullptr, 0, etc.)
}
return *this;
}Exemplo de Atribuição por Movimento
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data; // Steal the resource
size = other.size;
other.data = nullptr; // Leave source valid
other.size = 0;
}
return *this;
}Marque a atribuição por movimento como noexcept para um desempenho ideal com containers padrão.
Experimente você mesmo
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// Leia três strings da entrada
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// Crie três objetos DynamicString
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: Imprima o estado inicial de todas as três strings
// Formato:
// Inicial:
// str1: <value>
// str2: <value>
// str3: <value>
// TODO: Use atribuição por cópia: str1 = str2;
// TODO: Imprima str1 e str2 após a atribuição por cópia
// Formato:
// Após a cópia (str1 = str2):
// str1: <value>
// str2: <value>
// TODO: Use atribuição por movimento: str2 = std::move(str3);
// TODO: Imprima str2 após o movimento, e o comprimento de str3
// Formato:
// Após o movimento (str2 = std::move(str3)):
// str2: <value>
// comprimento de str3: 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 OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico