O que Padrões de Codificação C++ (Diretrizes Principais do C++) faz?
Padrões de codificação abrangentes para C++ moderno (C++17/20/23) derivados das Diretrizes Principais do C++. Aplica segurança de tipos, segurança de recursos, imutabilidade e clareza.
Quando Usar
- Escrevendo novo código C++ (classes, funções, templates)
- 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 (ex.:
enum vs enum class, ponteiro bruto vs smart pointer)
Quando NÃO Usar
- Projetos que não são C++
- Bases de código C legadas que não podem adotar recursos modernos do C++
- Contextos embarcados/bare-metal onde diretrizes específicas entram em conflito com restrições de hardware (adapte seletivamente)
Princípios Transversais
Esses temas recorrem em todas as diretrizes e formam a base:
- RAII em toda parte (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 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 tipo seguro |
| P.5 | Prefira verificação em tempo de compilação a 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 baixo o número de argumentos de função |
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 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 um 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 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 Classes (C.*)
Regras Principais
| Regra | Resumo |
|---|
| C.2 | Use class se um invariante existir; struct se os membros de dados variarem independentemente |
| C.9 | Minimize a exposição de membros |
| C.20 | Se você pode evitar definir operações padrão, faça-o (Regra de Zero) |
| C.21 | Se você definir ou =delete qualquer copy/move/destrutor, lide com todos (Regra de Cinco) |
| C.35 | Destrutor de classe base: virtual público ou não virtual protegido |
| C.41 | Um construtor deve criar um objeto totalmente inicializado |
| C.46 | Declare construtores de um único argumento 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 de Zero
struct Employee {
std::string name;
std::string department;
int id;
};
Regra de 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 Classes
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 sobrecarga (C.140)
- Tornar membros de dados
const ou referências, o que suprime move/copy (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 propriedade |
| 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 seja necessário compartilhar propriedade |
| R.22 | Use make_shared() para criar shared_ptrs |
Uso de Smart Pointers
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 puro (R.11)
malloc()/free() em código C++ (R.10)
- Múltiplas alocações de recursos 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 Instruções (ES.*)
Regras Principais
| Regra | Resumo |
|---|
| ES.5 | Mantenha escopos pequenos |
| ES.20 | Sempre inicialize um objeto |
| ES.23 | Prefira sintaxe de inicialização {} |
| ES.25 | Declare objetos como 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 restritivas/perda de dados |
| 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 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 sinal 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 realizar sua tarefa atribuída |
| E.6 | Use RAII para evitar vazamentos |
| E.12 | Use noexcept quando lançar for impossível ou inaceitável |
| E.14 | Use tipos definidos pelo usuário projetados especificamente 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 objetos imutáveis |
| Con.2 | Por padrão, torne 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 corridas 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::lock_guard<std::mutex> lock(mutex_);
queue_.push(value);
}
std::optional<int> pop() {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty()) return std::nullopt;
int value = queue_.front();
queue_.pop();
return value;
}
private:
std::queue<int> queue_;
mutable std::mutex mutex_;
};
void transfer(Account& from, Account& to, double amount) {
std::scoped_lock locks(from.mutex, to.mutex);
from.balance -= amount;
to.balance += amount;
}
Anti-Padrões
- Chamar
lock() sem unlock() correspondente (CP.20)
- Usar
volatile para sincronização entre threads (CP.8)
- Espera ocupada sem condição (CP.42)
- Não nomear instâncias de lock (CP.44)
- Programação livre de locks desnecessária (CP.100)
Templates e Programação Genérica (T.*)
Regras Principais
| Regra | Resumo |
|---|
| T.1 | Use templates para aumentar a abstração |
| T.10 | Especifique conceitos para todos os argumentos de template |
| T.20 | Evite dependências desnecessárias no contexto |
| T.41 | Requer apenas propriedades essenciais em parâmetros de template, não desnecessariamente completas |
| T.46 | Requer que os argumentos de template sejam tipos completos |
| T.47 | Evite parâmetros de template não tipados de alto nível |
| T.80 | Não use enable_if desnecessariamente |
Uso de Conceitos
template<typename T>
concept Hashable = requires(T a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
template<Hashable T>
void use_as_key(const T& value) {
auto hash = std::hash<T>{}(value);
}
Anti-Padrões
- Usar templates quando funções sobrecarregadas seriam suficientes (T.1)
- Não usar conceitos em código C++20 (T.10)
- Usar restrições complexas de
enable_if quando conceitos são mais claros (T.80)
Perfil de Segurança de Tipos (Type)
Regras
| Regra | Resumo |
|---|
| Type.1 | Evite casts |
| Type.2 | Use dynamic_cast apenas para navegar em hierarquias de herança |
| Type.3 | Use static_cast para conversões downcast que podem falhar |
| Type.4 | Use reinterpret_cast apenas em código de baixo nível próximo ao hardware |
| Type.5 | Use std::bit_cast para reinterpretar bits sem invocar UB |
int x = 42;
double y = static_cast<double>(x);
if (auto* derived = dynamic_cast<Derived*>(base_ptr)) {
derived->specific_method();
}
float f = 3.14f;
auto bits = std::bit_cast<std::uint32_t>(f);
Anti-Padrões
- Casts estilo C (Type.1)
reinterpret_cast em código de alto nível (Type.4)
Perfil de Segurança de Limites (Bounds)
Regras
| Regra | Resumo |
|---|
| Bounds.1 | Não acessar arrays além dos seus limites |
| Bounds.2 | Use contêineres da biblioteca padrão (como std::vector, std::array) para acionar verificações de limites |
| Bounds.3 | Use std::span e std::string_view para evitar ponteiros decaídos |
std::vector<int> vec = {1, 2, 3};
int val = vec.at(2);
void process(std::span<int> data) {
for (int& d : data) {
d = d * 2;
}
}
std::array<int, 5> arr = {1, 2, 3, 4, 5};
process(arr);
Anti-Padrões
- Acessar arrays estilo C com índice não verificado (Bounds.1)
- Passar
std::vector como ponteiro decaído + tamanho (Bounds.3)
Perfil de Segurança de Ciclo de Vida (Lifetime)
Regras
| Regra | Resumo |
|---|
| Lifetime.1 | Não retornar referências a objetos locais |
| Lifetime.2 | Não retornar ponteiros para objetos que já foram destruídos |
| Lifetime.3 | Não delegar de um ponteiro bruto para um smart pointer, a menos que a propriedade seja transferida |
std::string get_name() { return "example"; }
const std::string& get_name_bad() {
std::string name = "example";
return name;
}
std::unique_ptr<Widget> create_widget() {
return std::make_unique<Widget>();
}
void use_widget(Widget* w) {
if (w) w->do_something();
}
Anti-Padrões
- Retornar referências a objetos locais (Lifetime.1)
- Transferir propriedade via ponteiro bruto (Lifetime.3)
Convenções de Nomenclatura e Estilo (NL.*)
Regras Principais
| Regra | Resumo |
|---|
| NL.1 | Nomes devem comunicar intenção |
| NL.2 | Nomes devem ser legíveis e consistentes |
| NL.5 | Evite abreviações e siglas enigmáticas |
| NL.9 | Use capitalização consistente entre nomes |
| NL.10 | Use _ para separar palavras em nomes de funções e variáveis (snake_case) |
| NL.15 | Use nomes curtos para escopos pequenos, nomes longos para escopos grandes |
| NL.25 | Não use using namespace em escopo global em arquivos de cabeçalho |
Convenções Recomendadas
| Elemento | Convenção | Exemplo |
|---|
| Tipos (classes, structs, enums) | PascalCase | class WidgetRenderer |
| Funções e variáveis | snake_case | void render_widget(); |
| Constantes e macros | UPPER_SNAKE_CASE | const int MAX_SIZE = 100; |
| Parâmetros de template | PascalCase | template<typename T> |
| Membros privados | snake_case com _ sufixo | int value_; |
Anti-Padrões
- Abreviações como
cnt, idx, tmp (NL.5)
- Prefixos de tipo como
m_, pWidget (exceto membros privados com _)
using namespace std; em cabeçalhos (NL.25)
Comentários e Documentação
Regras
| Regra | Resumo |
|---|
| NL.1 | Comentários devem explicar "por que", não "como" |
| NL.2 | Comentários devem ser concisos e manter-se atualizados |
| NL.3 | Use documentação em linha quando útil |
| NL.4 | Use blocos de documentação para APIs públicas |
| NL.5 | Evite comentários óbvios que repetem o código |
Exemplos
auto shuffled = fisher_yates_shuffle(data);
counter++;
Organização de Arquivos
Regras
| Regra | Resumo |
|---|
| SF.1 | Use um sufixo para arquivos de implementação (.cpp) e cabeçalhos (.h ou .hpp) |
| SF.2 | Um arquivo .h não deve conter implementações de objeto ou funções não inline |
| SF.3 | Use #include com nomes de arquivo, não caminhos absolutos |
| SF.4 | Inclua o cabeçalho correspondente primeiro no arquivo .cpp |
| SF.5 | Um arquivo .cpp deve incluir o(s) cabeçalho(s) que define(m) sua interface |
| SF.6 | Use #pragma once ou guards de inclusão tradicionais para evitar inclusões múltiplas |
| SF.7 | Evite dependências de compilação desnecessárias entre cabeçalhos |
Exemplo
#pragma once
#include <string>
class Widget {
public:
explicit Widget(std::string name);
std::string name() const;
private:
std::string name_;
};
#include "widget.h"
#include <iostream>
Widget::Widget(std::string name) : name_(std::move(name)) {}
std::string Widget::name() const { return name_; }
Anti-Padrões
- Incluir arquivos
.cpp diretamente (SF.2)
- Misturar declaração e implementação em cabeçalhos para funções não inline (SF.2)
- Usar
#include com caminhos absolutos ou relativos complexos (SF.3)
Integração com C (CPL.*)
Regras
| Regra | Resumo |
|---|
| CPL.1 | Prefira C++ a C a menos que a interoperabilidade seja obrigatória |
| CPL.2 | Se você deve usar C, use a intersecção comum de C e C++ |
| CPL.3 | Use extern "C" para interfaces que precisam ser chamadas de C |
extern "C" {
void c_interface_function(int x);
double compute_value(double a, double b);
}
Anti-Padrões
- Usar
malloc/free em vez de new/delete ou smart pointers (CPL.1)
- Usar casts estilo C extensivamente (CPL.2)
Ferramentas de Apoio
Uso com Linters e Analisadores
- CLang-Tidy: Suporta a maioria das regras das Diretrizes Principais do C++
clang-tidy --checks="cppcoreguidelines-*" source.cpp
- CLang-Tidy com configuração personalizada: Use
.clang-tidy para ajustar regras
Checks: 'cppcoreguidelines-*,modernize-*'
WarningsAsErrors: '*'
HeaderFilterRegex: '.*'
CheckOptions:
cppcoreguidelines-avoid-magic-numbers: 'false'
- VS Code com extensão C++: Configurar
C_Cpp.codeAnalysis.clangTidy.enabled e adicionar "cppcoreguidelines-*" às verificações
- Visual Studio: Habilitar "Code Analysis" com regras de Diretrizes Principais do C++
- CMake com suporte a clang-tidy:
set(CMAKE_CXX_CLANG_TIDY "clang-tidy")
Integração Contínua
Para garantir a aplicação das diretrizes, adicione verificações de linting ao pipeline de CI:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install clang-tidy
run: sudo apt-get install -y clang-tidy
- name: Run clang-tidy
run: |
find . -name '*.cpp' -o -name '*.h' | xargs clang-tidy \
--checks="cppcoreguidelines-*,modernize-*" \
--warnings-as-errors="*"
Recursos Online