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:
#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é:
#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:
# 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();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?
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
- Postupujte podle základních pokynů C++ – RAII pro zdroje, pravidlo 5, silné psaní, minimální veřejná rozhraní API
- Nastavte vše
constjako 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 — use
perf, 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?