Padrões de Codificação C++ (Diretrizes Principais de C++)
Padrões de codificação abrangentes para C++ moderno (C++17/20/23) derivados das Diretrizes Principais de C++. Aplica segurança de tipos, segurança de recursos, imutabilidade e clareza.
Quando Usar
- Escrevendo novo código C++ (classes, funções, modelos)
- Revisando ou refatorando código C++ existente
- Tomando decisões arquiteturais em projetos C++
- Aplicando estilo consistente em uma base de código C++
- Escolhendo entre recursos da linguagem (por exemplo,
enum vs enum class, ponteiro bruto vs ponteiro inteligente)
Quando NÃO Usar
- Projetos que não são C++
- Bases de código C legadas que não podem adotar recursos modernos de C++
- Contextos embarcados/bare-metal onde diretrizes específicas entram em conflito com restrições de hardware (adaptar seletivamente)
Princípios Transversais
Esses temas se repetem em todas as diretrizes e formam a base:
- RAII em todo lugar (P.8, R.1, E.6, CP.20): Vincule o tempo de vida do recurso ao tempo de vida do objeto
- Imutabilidade por padrão (P.10, Con.1-5, ES.25): Comece com
const/constexpr; mutabilidade é a exceção
- Segurança de tipos (P.4, I.4, ES.46-49, Enum.3): Use o sistema de tipos para prevenir erros em tempo de compilação
- Expresse intenção (P.3, F.1, NL.1-2, T.10): Nomes, tipos e conceitos devem comunicar propósito
- Minimize a complexidade (F.2-3, ES.5, Per.4-5): Código simples é código correto
- Semântica de valor sobre semântica de ponteiro (C.10, R.3-5, F.20, CP.31): Prefira retornar por valor e objetos com escopo
Filosofia e Interfaces (P., I.)
Regras Principais
| Regra | Resumo |
|---|
| P.1 | Expresse ideias diretamente no código |
| P.3 | Expresse intenção |
| P.4 | Idealmente, um programa deve ser estaticamente seguro quanto ao tipo |
| P.5 | Prefira verificação em tempo de compilação à verificação em tempo de execução |
| P.8 | Não vaze nenhum recurso |
| P.10 | Prefira dados imutáveis a dados mutáveis |
| I.1 | Torne as interfaces explícitas |
| I.2 | Evite variáveis globais não-const |
| I.4 | Torne as interfaces precisas e fortemente tipadas |
| I.11 | Nunca transfira propriedade por um ponteiro bruto ou referência |
| I.23 | Mantenha o número de argumentos de função baixo |
FAÇA
struct Temperature {
double kelvin;
};
Temperature boil(const Temperature& water);
NÃO FAÇA
double boil(double* temp);
int g_counter = 0;
Funções (F.*)
Regras Principais
| Regra | Resumo |
|---|
| F.1 | Empacote operações significativas como funções cuidadosamente nomeadas |
| F.2 | Uma função deve realizar uma única operação lógica |
| F.3 | Mantenha as funções curtas e simples |
| F.4 | Se uma função pode ser avaliada em tempo de compilação, declare-a constexpr |
| F.6 | Se sua função não deve lançar exceções, declare-a noexcept |
| F.8 | Prefira funções puras |
| F.16 | Para parâmetros "de entrada", passe tipos baratos de copiar por valor e outros por const& |
| F.20 | Para valores "de saída", prefira valores de retorno a parâmetros de saída |
| F.21 | Para retornar múltiplos valores "de saída", prefira retornar uma struct |
| F.43 | Nunca retorne um ponteiro ou referência a um objeto local |
Passagem de Parâmetros
void print(int x);
void analyze(const std::string& data);
void transform(std::string s);
struct ParseResult {
std::string token;
int position;
};
ParseResult parse(std::string_view input);
void parse(std::string_view input,
std::string& token, int& pos);
Funções Puras e constexpr
constexpr int factorial(int n) noexcept {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);
Anti-Padrões
- Retornar
T&& de funções (F.45)
- Usar
va_arg / variádicos ao estilo C (F.55)
- Capturar por referência em lambdas passadas para outras threads (F.53)
- Retornar
const T que inibe a semântica de movimento (F.49)
Classes e Hierarquias de Classe (C.*)
Regras Principais
| Regra | Resumo |
|---|
| C.2 | Use class se invariante existe; struct se os membros de dados variam independentemente |
| C.9 | Minimize a exposição de membros |
| C.20 | Se você pode evitar definir operações padrão, faça (Regra do Zero) |
| C.21 | Se você define ou =delete qualquer copy/move/destrutor, lide com todos (Regra dos Cinco) |
| C.35 | Destrutor de classe base: virtual público ou protegido não-virtual |
| C.41 | Um construtor deve criar um objeto completamente inicializado |
| C.46 | Declare construtores de argumento único como explicit |
| C.67 | Uma classe polimórfica deve suprimir copy/move público |
| C.128 | Funções virtuais: especifique exatamente um de virtual, override ou final |
Regra do Zero
struct Employee {
std::string name;
std::string department;
int id;
};
Regra dos Cinco
class Buffer {
public:
explicit Buffer(std::size_t size)
: data_(std::make_unique<char[]>(size)), size_(size) {}
~Buffer() = default;
Buffer(const Buffer& other)
: data_(std::make_unique<char[]>(other.size_)), size_(other.size_) {
std::copy_n(other.data_.get(), size_, data_.get());
}
Buffer& operator=(const Buffer& other) {
if (this != &other) {
auto new_data = std::make_unique<char[]>(other.size_);
std::copy_n(other.data_.get(), other.size_, new_data.get());
data_ = std::move(new_data);
size_ = other.size_;
}
return *this;
}
Buffer(Buffer&&) noexcept = default;
Buffer& operator=(Buffer&&) noexcept = default;
private:
std::unique_ptr<char[]> data_;
std::size_t size_;
};
Hierarquia de Classe
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
class Circle : public Shape {
public:
explicit Circle(double r) : radius_(r) {}
double area() const override { return 3.14159 * radius_ * radius_; }
private:
double radius_;
};
Anti-Padrões
- Chamar funções virtuais em construtores/destrutores (C.82)
- Usar
memset/memcpy em tipos não triviais (C.90)
- Fornecer argumentos padrão diferentes para função virtual e sobrescrita (C.140)
- Tornar membros de dados
const ou referências, o que suprime movimento/cópia (C.12)
Gerenciamento de Recursos (R.*)
Regras Principais
| Regra | Resumo |
|---|
| R.1 | Gerencie recursos automaticamente usando RAII |
| R.3 | Um ponteiro bruto (T*) não possui |
| R.5 | Prefira objetos com escopo; não aloque no heap desnecessariamente |
| R.10 | Evite malloc()/free() |
| R.11 | Evite chamar new e delete explicitamente |
| R.20 | Use unique_ptr ou shared_ptr para representar propriedade |
| R.21 | Prefira unique_ptr a shared_ptr a menos que esteja compartilhando a propriedade |
| R.22 | Use make_shared() para criar shared_ptrs |
Uso de Ponteiros Inteligentes
auto widget = std::make_unique<Widget>("config");
auto cache = std::make_shared<Cache>(1024);
void render(const Widget* w) {
if (w) w->draw();
}
render(widget.get());
Padrão RAII
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: handle_(std::fopen(path.c_str(), "r")) {
if (!handle_) throw std::runtime_error("Falha ao abrir: " + path);
}
~FileHandle() {
if (handle_) std::fclose(handle_);
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&& other) noexcept
: handle_(std::exchange(other.handle_, nullptr)) {}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (handle_) std::fclose(handle_);
handle_ = std::exchange(other.handle_, nullptr);
}
return *this;
}
private:
std::FILE* handle_;
};
Anti-Padrões
new/delete puros (R.11)
malloc()/free() em código C++ (R.10)
- Múltiplas alocações de recurso em uma única expressão (R.13 -- risco de segurança de exceção)
shared_ptr onde unique_ptr é suficiente (R.21)
Expressões e Declarações (ES.*)
Regras Principais
| Regra | Resumo |
|---|
| ES.5 | Mantenha os escopos pequenos |
| ES.20 | Sempre inicialize um objeto |
| ES.23 | Prefira sintaxe de inicializador {} |
| ES.25 | Declare objetos const ou constexpr a menos que a modificação seja pretendida |
| ES.28 | Use lambdas para inicialização complexa de variáveis const |
| ES.45 | Evite constantes mágicas; use constantes simbólicas |
| ES.46 | Evite conversões aritméticas de estreitamento/com perda |
| ES.47 | Use nullptr em vez de 0 ou NULL |
| ES.48 | Evite casts |
| ES.50 | Não remova const com cast |
Inicialização
const int max_retries{3};
const std::string name{"widget"};
const std::vector<int> primes{2, 3, 5, 7, 11};
const auto config = [&] {
Config c;
c.timeout = std::chrono::seconds{30};
c.retries = max_retries;
c.verbose = debug_mode;
return c;
}();
Anti-Padrões
- Variáveis não inicializadas (ES.20)
- Usar
0 ou NULL como ponteiro (ES.47 -- use nullptr)
- Casts ao estilo C (ES.48 -- use
static_cast, const_cast, etc.)
- Remover
const com cast (ES.50)
- Números mágicos sem constantes nomeadas (ES.45)
- Misturar aritmética com e sem sinal (ES.100)
- Reutilizar nomes em escopos aninhados (ES.12)
Tratamento de Erros (E.*)
Regras Principais
| Regra | Resumo |
|---|
| E.1 | Desenvolva uma estratégia de tratamento de erros no início do design |
| E.2 | Lance uma exceção para sinalizar que uma função não pode executar sua tarefa atribuída |
| E.6 | Use RAII para evitar vazamentos |
| E.12 | Use noexcept quando lançar é impossível ou inaceitável |
| E.14 | Use tipos definidos pelo usuário projetados para o propósito como exceções |
| E.15 | Lance por valor, capture por referência |
| E.16 | Destrutores, desalocação e swap nunca devem falhar |
| E.17 | Não tente capturar toda exceção em toda função |
Hierarquia de Exceções
class AppError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class NetworkError : public AppError {
public:
NetworkError(const std::string& msg, int code)
: AppError(msg), status_code(code) {}
int status_code;
};
void fetch_data(const std::string& url) {
throw NetworkError("conexão recusada", 503);
}
void run() {
try {
fetch_data("https://api.example.com");
} catch (const NetworkError& e) {
log_error(e.what(), e.status_code);
} catch (const AppError& e) {
log_error(e.what());
}
}
Anti-Padrões
- Lançar tipos embutidos como
int ou literais de string (E.14)
- Capturar por valor (risco de slicing) (E.15)
- Blocos catch vazios que engolem erros silenciosamente
- Usar exceções para controle de fluxo (E.3)
- Tratamento de erros baseado em estado global como
errno (E.28)
Constantes e Imutabilidade (Con.*)
Todas as Regras
| Regra | Resumo |
|---|
| Con.1 | Por padrão, torne os objetos imutáveis |
| Con.2 | Por padrão, torne as funções membro const |
| Con.3 | Por padrão, passe ponteiros e referências para const |
| Con.4 | Use const para valores que não mudam após a construção |
| Con.5 | Use constexpr para valores computáveis em tempo de compilação |
class Sensor {
public:
explicit Sensor(std::string id) : id_(std::move(id)) {}
const std::string& id() const { return id_; }
double last_reading() const { return reading_; }
void record(double value) { reading_ = value; }
private:
const std::string id_;
double reading_{0.0};
};
void display(const Sensor& s) {
std::cout << s.id() << ": " << s.last_reading() << '\n';
}
constexpr double PI = 3.14159265358979;
constexpr int MAX_SENSORS = 256;
Concorrência e Paralelismo (CP.*)
Regras Principais
| Regra | Resumo |
|---|
| CP.2 | Evite condições de corrida de dados |
| CP.3 | Minimize o compartilhamento explícito de dados graváveis |
| CP.4 | Pense em termos de tarefas, em vez de threads |
| CP.8 | Não use volatile para sincronização |
| CP.20 | Use RAII, nunca lock()/unlock() puro |
| CP.21 | Use std::scoped_lock para adquirir múltiplos mutexes |
| CP.22 | Nunca chame código desconhecido enquanto segura um lock |
| CP.42 | Não espere sem uma condição |
| CP.44 | Lembre-se de nomear seus lock_guards e unique_locks |
| CP.100 | Não use programação livre de locks a menos que seja absolutamente necessário |
Bloqueio Seguro
class ThreadSafeQueue {
public:
void push(int value) {
std::scoped_lock lock(mutex_);
queue_.push(value);
}
std::optional<int> pop() {
std::scoped_lock lock(mutex_);
if (queue_.empty()) return std::nullopt;
int val = queue_.front();
queue_.pop();
return val;
}
private:
std::queue<int> queue_;
std::mutex mutex_;
};
Esta Skill Aplica
- Todos os padrões C++ Core Guidelines mencionados
- RAII, imutabilidade, segurança de tipos
- Regras de funções, classes, gerenciamento de recursos, erros e concorrência
- Anti-padrões para evitar armadilhas comuns