Orientação a objetos
Assim como na seção de introdução à linguagem, orientação a objetos já deve ser algo que vocês dominam razoavelmente bem. Sendo assim, vou focar em mostrar como esses conceitos se manifestam em C++.
Classes e Structs
Section titled “Classes e Structs”Em C++, a única diferença entre class e struct é o acesso padrão dos
membros. Em uma class, todos os membros são private por padrão, enquanto
em um struct são public. Na prática, usamos struct para pacotes simples
de dados e class quando precisamos de encapsulamento e lógica.
class Pessoa {private: std::string nome; int idade;
public: // Construtor com lista de inicialização Pessoa(std::string n, int i) : nome(std::move(n)), idade(i) {}
// Método const: garante que não altera o objeto void saudar() const { std::cout << "Olá, meu nome é " << nome << "\n"; }};Construtores e Destrutores
Section titled “Construtores e Destrutores”C++ moderno favorece a lista de inicialização no construtor por ser mais eficiente. Ela evita a construção padrão seguida de atribuição, inicializando diretamente os membros.
Use explicit em construtores de um único parâmetro para impedir conversões
implícitas indesejadas. O destrutor, marcado como ~Classe(), é chamado
automaticamente quando o objeto sai de escopo graças ao princípio RAII.
class Recurso {public: explicit Recurso(int id) { /* ... */ } ~Recurso() { /* limpeza automática aqui */ }};A Regra do Zero
Section titled “A Regra do Zero”A Regra do Zero estabelece que você não deve escrever destrutores, construtores de cópia ou operadores de atribuição manualmente se puder compor sua classe com tipos da biblioteca padrão que já gerenciam esses aspectos corretamente.
struct Registro { std::string nome; std::vector<int> notas; // O compilador gera automaticamente cópia e movimento corretos aqui.};Herança e Polimorfismo
Section titled “Herança e Polimorfismo”Para usar polimorfismo em C++, você precisa marcar métodos como virtual,
sempre definir um destrutor virtual na classe base, e acessar objetos
através de ponteiros ou referências.
O destrutor virtual é essencial para evitar vazamentos quando deletamos um
objeto derivado através de um ponteiro para a classe base. A palavra-chave
override garante que você está realmente sobrescrevendo um método virtual
da classe base, gerando erro de compilação se não for o caso.
class Forma {public: // Essencial para evitar vazamentos em classes derivadas virtual ~Forma() = default; virtual double area() const = 0; // Método puro (classe abstrata)};
class Quadrado : public Forma {private: double lado;public: Quadrado(double l) : lado(l) {} double area() const override { return lado * lado; }};O Problema do Slicing
Section titled “O Problema do Slicing”Se você passar um objeto por valor em uma hierarquia de classes, ocorre o
slicing (fatiamento): a parte específica da classe derivada é descartada e
apenas a porção da classe base é copiada. Para evitar isso, sempre passe
objetos polimórficos por referência (Base&) ou usando smart pointers.
void imprimirArea(const Forma& f) { // Correto: usa referência std::cout << f.area() << "\n";}
int main() { auto q = std::make_unique<Quadrado>(5.0); imprimirArea(*q);}Polimorfismo com Smart Pointers
Section titled “Polimorfismo com Smart Pointers”A forma mais comum e segura de gerenciar coleções de objetos heterogêneos é
usando std::vector combinado com std::unique_ptr. Isso permite armazenar
diferentes tipos derivados em um mesmo container com gerenciamento automático
de memória.
#include <vector>#include <memory>
int main() { std::vector<std::unique_ptr<Forma>> formas; formas.push_back(std::make_unique<Quadrado>(10.0)); // formas.push_back(std::make_unique<Circulo>(5.0));
for (const auto& f : formas) { std::cout << f->area() << "\n"; }}Boas práticas
Section titled “Boas práticas”Ao escrever classes em C++, prefira sempre usar listas de inicialização nos
construtores e marque construtores de um argumento como explicit. Quando
sobrescrever métodos virtuais, use override para garantir a correção.
Sempre defina um destrutor virtual em classes que servem de base para outras. Prefira composição em vez de herança quando possível, e nunca esqueça que polimorfismo só funciona corretamente quando você usa ponteiros ou referências, evitando assim o problema do slicing.