Přeskočit na obsah

Základní pokyny a profesionální vzory C++

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

Writing Professional C++ Code

Writing correct, efficient C++ is necessary but not sufficient for professional software development. Professional C++ also means code that is readable (understandable by others and your future self), maintainable (easy to modify without introducing bugs), and robust (handles errors gracefully and fails predictably). The C++ Core Guidelines, maintained by Bjarne Stroustrup and Herb Sutter, codify decades of hard-won wisdom into actionable rules. This lesson covers the most impactful guidelines and professional practices.

Přehled základních pokynů C++

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 Correctness as a Discipline

Konstantní správnost není jen o zabránění náhodným modifikacím – dokumentuje záměr, umožňuje optimalizaci kompilátoru a standardně činí kód bezpečným pro vlákna. Udělejte všeconst, pokud to nemusí být proměnlivé:

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;
}

Principy návrhu API

Zlaté pravidlo designu C++ API, připisované Scottu Meyersovi: Umožněte rozhraní snadno používat správně a obtížněji používat nesprávně.

Používejte silné typy: Místovoid set_timeout(int ms)použijtevoid set_timeout(std::chrono::milliseconds timeout). Volající nemůže náhodně předat sekundy, když očekáváte milisekundy.

Použijte[[nodiscard]]: Jakákoli funkce, která vrací kód chyby, zdroj nebo vypočítanou hodnotu, by měla být[[nodiscard]]. Ignorování návratové hodnoty je téměř vždy chyba.

Upřednostňovat sémantiku hodnoty: Předávejte malé typy hodnotou, větší typyconst&. Návrat podle hodnoty (překladač použije copy elision). Vyhněte se výstupním parametrům.

Minimalizujte veřejnou plochu: Každý veřejný člen je závazek, který musíte dodržovat. Ve výchozím nastavení používejteprivatea vystavujte pouze to, co je nezbytné.

Řiďte se zásadou nejmenšího překvapení: Funkce by měly dělat to, co napovídá jejich název, nic víc. Nežádoucí účinky by měly být zřejmé z názvu (save_to_file, nikoliprocess).

Konvence pojmenování a dokumentace

Díky konzistentnímu pojmenování je kód čitelný v celém týmu. Společné konvence C++:

Typy:PascalCase—class HttpClient,struct Point3D,enum class Color
Funkce/Metody:snake_casenebocamelCase—calculate_total(),getSize()
Proměnné:snake_case—int item_count,double total_price
Členské proměnné: podtržítko na konci —int count_,std::string name_
Konstanty: předponakneboUPPER_SNAKE—constexpr int kMaxRetries = 3
Jmenné prostory:lowercase—namespace network,namespace detail

Vyberte jednu konvenci a prosaďte ji v celém projektu (formát clang + clang-tidy to dokáže zautomatizovat).

Dokumentace: Použijte komentáře ve stylu Doxygen pro veřejná rozhraní API:

/// @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);

Profilování výkonu

Nikdy neprovádějte optimalizaci bez předchozího profilování. Tyto nástroje vám řeknou, kde se skutečně tráví čas, a nahradí dohady daty:

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();
Osvědčený postup

Při kontrole kódu C++ zkontrolujte tyto běžné problémy:

1. Správa zdrojů: Spravuje každý zdroj (paměť, soubory, zámky) RAII? Nějaký nezpracovanýnew/delete?
2. Konstantní správnost: Jsou parametry, členské funkce a lokální proměnnéconsttam, kde je to možné?
3. Ošetření chyb: Kontrolují se návratové hodnoty? Jsou výjimky zachyceny na odpovídající úrovni? Používá sestd::expectednebo chybové kódy důsledně?
4. Bezpečnost vláken: Jsou sdílené zdroje chráněny? Jsou datové závody možné? Zachytil by dezinfekční prostředek problémy?
5. Sémantika přesunu: Jsou objekty přesunuty místo zkopírování tam, kde je to vhodné? Jsou volánístd::movena const objektech (které se tiše kopírují)?
6. Jasnost API: Jsou názvy funkcí popisné? Jsou parametry silně zadané? Používá se[[nodiscard]]na důležité návratové hodnoty?
7. Upozornění na sestavení: Kompiluje se kód čistě s-Wall -Wextra -Wpedantic? Jsou všechna varování adresována, nikoli potlačována?
8. Testování: Testují se pouzdra hran? Jsou nové cesty kódu pokryty testy? Projdou dezinfekční prostředky?

Úskalí

Ladění C++ ve výrobě je těžší než ve vývoji. Připravte se na to:

Vždy dodávejte se symboly ladění: SestavteRelWithDebInfopro produkční binární soubory. Ladicí symboly v samostatném souboru.debugpřidávají nulové provozní náklady, ale umožňují analýzu selhání.

Strukturovaná data protokolů: Použijte strukturované protokolování (JSON) s úrovněmi protokolů. Zahrňte časová razítka, ID vláken a ID požadavků. Použijtespdlognebostd::formatpro rychlé a typově bezpečné protokolování.

Výpisy jádra: Povolte výpisy jádra (ulimit -c unlimited) a zjistěte, jak je analyzovat pomocígdb. Symbolové servery ukládají informace o ladění pro nasazené binární soubory.

Běžné produkční chyby:
- Úniky paměti — Používejte ASan ve fázovacích prostředích nebo nástroje jako Valgrind / Heaptrack
- Zablokování — Použijte TSan při testování, logujte pořadí akvizice zámku, nastavte časové limity zámku
- Regrese výkonu – Průběžné srovnávání v CI detekuje regrese před nasazením
- Kompatibilita ABI — Změna rozložení struktur nebo tabulek virtuálních funkcí přeruší ABI; pro stabilní ABI použijte idiom Pimpl

Klíčové poznatky
  • Postupujte podle základních pokynů C++ – RAII pro zdroje, pravidlo 5, silné psaní, minimální veřejná rozhraní API
  • Nastavte všeconstjako výchozí – dokumentuje záměr, zabraňuje chybám a umožňuje optimalizace
  • Návrhová rozhraní API, která se snadno a správně používají a obtížně se používají nesprávně — silné typy,[[nodiscard]], sémantika hodnot
  • Profile before optimizing — useperf, Instruments, or Google Benchmark to find real bottlenecks
  • 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

Kvíz — Otestujte své znalosti

(15 XP)

1. Co uvádí základní směrnice CPLUS „Pravidlo 5“?

2. Jaké je zlaté pravidlo návrhu C++ API?

3. Proč byste měli před optimalizací profilovat?