Sari la conținut

C++ Core Guidelines & Professional Patterns

Learn the C++ Core Guidelines, naming conventions, const correctness, API design principles, documentation practices, performance profiling, and production debugging techniques.

Writing Professional C++ Code

Scrierea corectă și eficientă a C++ este necesară, dar nu suficientă pentru dezvoltarea software-ului profesional. Professional C++ înseamnă, de asemenea, cod care este lizibil (înțeles de ceilalți și de viitorul tău sine), de întreținere (ușor de modificat fără a introduce erori) și robust (tratează erorile cu grație și eșuează previzibil). Liniile directoare de bază C++, menținute de Bjarne Stroustrup și Herb Sutter, codifică zeci de ani de înțelepciune câștigată cu greu în reguli aplicabile. Această lecție acoperă cele mai importante orientări și practici profesionale.

C++ Core Guidelines Overview

The Core Guidelines cover resource management, interfaces, error handling, and naming. Here are some of the most important rules in practice:

core_guidelines.cpp
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <span>
#include <stdexcept>

// R.1: Manage resources automatically using RAII
class DatabaseConnection {
public:
    explicit DatabaseConnection(const std::string& connection_string) {
        // Acquire resource in constructor
        std::cout << "Connected to: " << connection_string << "\n";
    }
    ~DatabaseConnection() {
        // Release resource in destructor — always runs
        std::cout << "Disconnected\n";
    }

    // Rule of 5: if you define a destructor, define or delete all 5
    DatabaseConnection(const DatabaseConnection&) = delete;
    DatabaseConnection& operator=(const DatabaseConnection&) = delete;
    DatabaseConnection(DatabaseConnection&&) noexcept = default;
    DatabaseConnection& operator=(DatabaseConnection&&) noexcept = default;

    void query(std::string_view sql) const {
        std::cout << "Executing: " << sql << "\n";
    }
};

// I.4: Make interfaces precisely and strongly typed
// BAD: void process(int type, void* data, int size);
// GOOD:
enum class DataType { json, xml, csv };

void process(DataType type, std::span<const std::byte> data) {
    // type-safe, no raw pointers, size is carried with the data
}

// F.3: Keep functions short and simple
[[nodiscard]] bool is_valid_email(std::string_view email) {
    auto at_pos = email.find('@');
    if (at_pos == std::string_view::npos) return false;
    auto dot_pos = email.find('.', at_pos);
    return dot_pos != std::string_view::npos && dot_pos > at_pos + 1;
}

int main() {
    // R.5: Prefer scoped objects — no unnecessary heap allocation
    DatabaseConnection db("host=localhost dbname=mydb");
    db.query("SELECT * FROM users");
    // db is automatically closed when it goes out of scope

    // R.11: Avoid raw new/delete — use smart pointers
    auto widget = std::make_unique<std::vector<int>>(100);

    std::cout << is_valid_email("user@example.com") << "\n";   // 1
    std::cout << is_valid_email("invalid-email") << "\n";       // 0

    return 0;
}

Const Corectitudinea ca disciplină

Const correctness is not just about preventing accidental modification — it documents intent, enables compiler optimizations, and makes code thread-safe by default. Make everythingconstunless it needs to be mutable:

const_correctness.cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

class StudentRegistry {
public:
    // const method — promises not to modify the object
    [[nodiscard]] size_t count() const { return students_.size(); }

    // const reference return — caller can read but not modify
    [[nodiscard]] const std::string& get_name(size_t index) const {
        if (index >= students_.size())
            throw std::out_of_range("Invalid student index");
        return students_[index];
    }

    // Non-const method — modifies the object
    void add_student(std::string name) {
        students_.push_back(std::move(name));
    }

    // const-correct algorithm usage
    [[nodiscard]] bool has_student(std::string_view name) const {
        return std::any_of(students_.cbegin(), students_.cend(),
            [name](const std::string& s) { return s == name; });
    }

private:
    std::vector<std::string> students_;
};

void print_registry(const StudentRegistry& reg) {
    // Can only call const methods on a const reference
    for (size_t i = 0; i < reg.count(); ++i) {
        std::cout << reg.get_name(i) << "\n";
    }
    // reg.add_student("Eve");  // ERROR: cannot call non-const method
}

int main() {
    StudentRegistry reg;
    reg.add_student("Alice");
    reg.add_student("Bob");

    print_registry(reg);

    // const local variables
    const auto count = reg.count();  // immutable
    const auto& name = reg.get_name(0);  // immutable reference

    std::cout << "Count: " << count << ", First: " << name << "\n";

    return 0;
}

API Design Principles

The golden rule of C++ API design, attributed to Scott Meyers: Make interfaces easy to use correctly and hard to use incorrectly.

Use strong types: Instead ofvoid set_timeout(int ms), usevoid set_timeout(std::chrono::milliseconds timeout). The caller cannot accidentally pass seconds when you expect milliseconds.

Use[[nodiscard]]: Any function that returns an error code, a resource, or a computed value should be[[nodiscard]]. Ignoring the return value is almost always a bug.

Prefer value semantics: Pass small types by value, larger types byconst&. Return by value (the compiler applies copy elision). Avoid output parameters.

Minimize public surface area: Every public member is a commitment you must maintain. Useprivateby default and only expose what's necessary.

Follow the principle of least surprise: Functions should do what their name implies, nothing more. Side effects should be obvious from the name (save_to_file, notprocess).

Convenții de denumire și documentație

Consistent naming makes code readable across a team. Common C++ conventions:

Types:PascalCase—class HttpClient,struct Point3D,enum class Color
Functions/Methods:snake_caseorcamelCase—calculate_total(),getSize()
Variables:snake_case—int item_count,double total_price
Member variables: trailing underscore —int count_,std::string name_
Constants:kprefix orUPPER_SNAKE—constexpr int kMaxRetries = 3
Namespaces:lowercase—namespace network,namespace detail

Pick one convention and enforce it project-wide (clang-format + clang-tidy can automate this).

Documentation: Use Doxygen-style comments for public APIs:

/// @brief Connects to the database.
/// @param connection_string The database connection URI.
/// @return true if connection succeeded.
/// @throws DatabaseError if the connection fails.
[[nodiscard]] bool connect(std::string_view connection_string);

Profilarea performanței

Never optimize without profiling first. These tools tell you where time is actually spent, replacing guesswork with data:

benchmark.cpp
# Linux: perf — hardware performance counters
perf record -g ./my_program         # record call graph
perf report                           # interactive flamegraph viewer

# Linux: perf stat — high-level statistics
perf stat ./my_program
# Output: cycles, instructions, cache misses, branch misses, IPC

# macOS: Instruments (via Xcode)
xcrun xctrace record --template 'Time Profiler' --launch ./my_program

# Cross-platform: Google Benchmark (micro-benchmarking)
# In your code:
#include <benchmark/benchmark.h>

static void BM_VectorPushBack(benchmark::State& state) {
    for (auto _ : state) {
        std::vector<int> v;
        for (int i = 0; i < state.range(0); ++i) {
            v.push_back(i);
        }
        benchmark::DoNotOptimize(v.data());
    }
}
BENCHMARK(BM_VectorPushBack)->Range(8, 1 << 16);

static void BM_VectorReserved(benchmark::State& state) {
    for (auto _ : state) {
        std::vector<int> v;
        v.reserve(state.range(0));
        for (int i = 0; i < state.range(0); ++i) {
            v.push_back(i);
        }
        benchmark::DoNotOptimize(v.data());
    }
}
BENCHMARK(BM_VectorReserved)->Range(8, 1 << 16);

BENCHMARK_MAIN();
Bună practică

Când examinați codul C++, verificați aceste probleme comune:

1. Gestionarea resurselor: fiecare resursă (memorie, fișiere, lacăte) este gestionată de RAII? Vreunnew/deletebrut?
2. Corectitatea constantă: parametrii, funcțiile membre și variabilele locale suntconstacolo unde este posibil?
3. Gestionarea erorilor: sunt verificate valorile returnate? Sunt excepțiile prinse la niveluri adecvate?std::expectedsau codurile de eroare sunt folosite în mod constant?
4. Siguranța firelor: resursele partajate sunt protejate? Sunt posibile cursele de date? Ar prinde probleme un dezinfectant?
5. Mutați semantica: sunt obiectele mutate în loc de copiate acolo unde este cazul? Sunt apeluristd::movepe obiecte const (care copiază în tăcere)?
6. Claritatea API: numele funcțiilor sunt descriptive? Parametrii sunt tastați puternic? Este[[nodiscard]]folosit pentru valorile de returnare importante?
7. Construiți avertismente: se compila corect codul cu-Wall -Wextra -Wpedantic? Sunt toate avertismentele abordate, nu suprimate?
8. Testare: sunt testate carcasele marginale? Sunt noile căi de cod acoperite de teste? Trec dezinfectanții?

Capcană

Depanarea C++ în producție este mai dificilă decât în ​​dezvoltare. Pregătește-te pentru asta:

Livrați întotdeauna cu simboluri de depanare: construițiRelWithDebInfopentru binarele de producție. Simbolurile de depanare dintr-un fișier.debugseparat nu adaugă nicio suprasarcină de rulare, dar fac posibilă analiza blocării.

Datele structurate în jurnal: utilizați jurnalul structurat (JSON) cu niveluri de jurnal. Includeți marcajele de timp, ID-urile firelor și ID-urile cererii. Utilizațispdlogsaustd::formatpentru o înregistrare rapidă și sigură.

Core dumps: Activați core dumps (ulimit -c unlimited) și știți cum să le analizați cugdb. Serverele de simboluri stochează informații de depanare pentru binarele implementate.

Erori comune de producție:
- Scurgeri de memorie — Utilizați ASan în medii de pregătire sau instrumente precum Valgrind / Heaptrack
- Deadlocks — Utilizați TSan în testare, înregistrați ordinea de achiziție a blocării, setați intervale de timp de blocare
- Regresii de performanță — Evaluarea comparativă continuă în CI detectează regresiile înainte de implementare
- Compatibilitate ABI — Modificarea aspectului structurii sau a tabelelor de funcții virtuale întrerupe ABI; utilizați idiomul Pimpl pentru ABI-uri stabile

Idei cheie
  • Follow the C++ Core Guidelines — RAII for resources, Rule of 5, strong typing, minimal public APIs
  • Make everythingconstby default — it documents intent, prevents bugs, and enables optimizations
  • Design APIs that are easy to use correctly and hard to use incorrectly — strong types,[[nodiscard]], value semantics
  • Profil înainte de optimizare — utilizațiperf, Instruments sau Google Benchmark pentru a găsi blocaje reale
  • Review code for resource management, const correctness, thread safety, and proper error handling
  • Prepare for production debugging: ship with debug symbols, use structured logging, enable core dumps

Test — Verifică-ți cunoștințele

(15 XP)

1. What does the C++ Core Guideline 'Rule of 5' state?

2. What is the golden rule of C++ API design?

3. Why should you profile before optimizing?