راهنمای اصلی C++ و الگوهای حرفهای
راهنمای اصلی C++ (C++ Core Guidelines)، قراردادهای نامگذاری، صحت const، اصول طراحی API، شیوههای مستندسازی، پروفایل عملکرد، و تکنیکهای اشکالزدایی تولیدی را بیاموزید.
نوشتن کد C++ حرفهای
نوشتن C++ صحیح و کارآمد ضروری است اما برای توسعه نرمافزار حرفهای کافی نیست. C++ حرفهای همچنین به معنای کدی است که خوانا (قابل درک توسط دیگران و خود آیندهتان)، قابل نگهداری (آسان برای تغییر بدون معرفی باگ)، و مقاوم (خطاها را با ظرافت مدیریت میکند و بهصورت قابل پیشبینی شکست میخورد) باشد. راهنمای اصلی C++ (C++ Core Guidelines) که توسط Bjarne Stroustrup و Herb Sutter نگهداری میشود، دههها حکمت سختآموخته را در قوانین عملیاتی کدبندی میکند. این درس مؤثرترین راهنماها و شیوههای حرفهای را پوشش میدهد.
مروری بر راهنمای اصلی C++
راهنمای اصلی (Core Guidelines) مدیریت منابع، رابطها، مدیریت خطا و نامگذاری را پوشش میدهد. در اینجا برخی از مهمترین قوانین در عمل آمده است:
#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 بهعنوان یک نظم
صحت const فقط درباره جلوگیری از تغییر تصادفی نیست — نیت را مستند میکند، بهینهسازیهای کامپایلر را ممکن میسازد، و کد را بهصورت پیشفرض ایمن از نظر نخ میکند. همه چیز را const کنید مگر اینکه نیاز به تغییرپذیری داشته باشد:
#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
قانون طلایی طراحی API در C++، منسوب به Scott Meyers: رابطها را آسان برای استفاده صحیح و دشوار برای استفاده نادرست طراحی کنید.
از انواع قوی استفاده کنید: بهجای void set_timeout(int ms) از void set_timeout(std::chrono::milliseconds timeout) استفاده کنید. فراخواننده نمیتواند بهطور تصادفی ثانیه بفرستد وقتی شما میلیثانیه انتظار دارید.
از [[nodiscard]] استفاده کنید: هر تابعی که کد خطا، منبع یا مقدار محاسبهشده برمیگرداند باید [[nodiscard]] باشد. نادیدهگرفتن مقدار بازگشتی تقریباً همیشه باگ است.
معنابخشی مقداری را ترجیح دهید: انواع کوچک را بهصورت مقدار و انواع بزرگتر را بهصورت const& ارسال کنید. بهصورت مقدار برگردانید (کامپایلر حذف کپی اعمال میکند). از پارامترهای خروجی اجتناب کنید.
سطح عمومی را حداقل کنید: هر عضو عمومی تعهدی است که باید حفظ شود. بهصورت پیشفرض از private استفاده کنید و فقط آنچه ضروری است را نمایان کنید.
اصل کمترین شگفتی را دنبال کنید: توابع باید آنچه نامشان دلالت میکند انجام دهند، نه بیشتر. اثرات جانبی باید از نام آشکار باشند (save_to_file، نه process).
قراردادهای نامگذاری و مستندسازی
نامگذاری یکنواخت کد را در سطح تیم خوانا میکند. قراردادهای رایج C++:
انواع: PascalCase — class HttpClient، struct Point3D، enum class Color
توابع/متدها: snake_case یا camelCase — calculate_total()، getSize()
متغیرها: snake_case — int item_count، double total_price
متغیرهای عضو: زیرخط انتهایی — int count_، std::string name_
ثوابت: پیشوند k یا UPPER_SNAKE — constexpr int kMaxRetries = 3
فضاهای نام: lowercase — namespace network، namespace detail
یک قرارداد انتخاب کنید و آن را در سطح پروژه اعمال کنید (clang-format + clang-tidy میتوانند این را خودکار کنند).
مستندسازی: از نظرات به سبک Doxygen برای 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);پروفایل عملکرد
هرگز بدون پروفایل بهینهسازی نکنید. این ابزارها به شما میگویند زمان واقعاً کجا صرف میشود و حدس را با داده جایگزین میکنند:
# 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();هنگام بازبینی کد C++، این مسائل رایج را بررسی کنید:
1. مدیریت منابع: آیا هر منبع (حافظه، فایلها، قفلها) توسط RAII مدیریت میشود؟ آیا new/delete خام وجود دارد؟
2. صحت const: آیا پارامترها، توابع عضو و متغیرهای محلی تا حد ممکن const هستند؟
3. مدیریت خطا: آیا مقادیر بازگشتی بررسی میشوند؟ آیا استثناها در سطوح مناسب گرفته میشوند؟ آیا std::expected یا کدهای خطا بهصورت یکنواخت استفاده میشوند؟
4. ایمنی نخ: آیا منابع مشترک محافظت میشوند؟ آیا مسابقه داده ممکن است؟ آیا سنیتایزر مشکلات را میگیرد؟
5. معنابخشی انتقال: آیا اشیا بهجای کپی، جایی که مناسب است منتقل (move) میشوند؟ آیا فراخوانیهای std::move بر روی اشیای const وجود دارد (که بیصدا کپی میکنند)؟
6. وضوح API: آیا نام توابع توصیفی هستند؟ آیا پارامترها نوع قوی دارند؟ آیا [[nodiscard]] بر روی مقادیر بازگشتی مهم استفاده شده؟
7. هشدارهای ساخت: آیا کد با -Wall -Wextra -Wpedantic بدون هشدار کامپایل میشود؟ آیا تمام هشدارها رسیدگی شدهاند، نه سرکوب شدهاند؟
8. تست: آیا حالات لبهای تست شدهاند؟ آیا مسیرهای کد جدید تحت پوشش تست هستند؟ آیا سنیتایزرها موفق میشوند؟
اشکالزدایی C++ در محیط تولیدی سختتر از توسعه است. برای آن آماده شوید:
همیشه با نمادهای اشکالزدایی ارسال کنید: باینریهای تولیدی را با RelWithDebInfo بسازید. نمادهای اشکالزدایی در یک فایل .debug جداگانه هیچ سربار زمان اجرایی اضافه نمیکنند اما تحلیل خرابی را ممکن میسازند.
دادههای ساختاریافته ثبت کنید: از ثبتوقایع ساختاریافته (JSON) با سطوح ثبتوقایع استفاده کنید. زماننشانها، شناسههای نخ و شناسههای درخواست را شامل کنید. از spdlog یا std::format برای ثبتوقایع سریع و ایمن از نظر نوع استفاده کنید.
تخلیه هسته (Core Dumps): تخلیه هسته را فعال کنید (ulimit -c unlimited) و بدانید چگونه آنها را با gdb تحلیل کنید. سرورهای نماد، اطلاعات اشکالزدایی را برای باینریهای مستقرشده ذخیره میکنند.
باگهای رایج تولیدی:
- نشت حافظه — از ASan در محیطهای آمادهسازی یا ابزارهایی مانند Valgrind / Heaptrack استفاده کنید
- بنبست (Deadlock) — از TSan در تست استفاده کنید، ترتیب گرفتن قفل را ثبت کنید، زمانبندی قفل تعیین کنید
- افت عملکرد — محکزنی مداوم در CI افت عملکرد را قبل از استقرار تشخیص میدهد
- سازگاری ABI — تغییر چیدمان ساختارها یا جداول توابع مجازی ABI را خراب میکند؛ از الگوی Pimpl برای ABIهای پایدار استفاده کنید
- راهنمای اصلی C++ را دنبال کنید — RAII برای منابع، قانون ۵، نوعگذاری قوی، APIهای عمومی حداقلی
- همه چیز را بهصورت پیشفرض
constکنید — نیت را مستند میکند، از باگها جلوگیری میکند، و بهینهسازیها را ممکن میسازد - APIها را طوری طراحی کنید که استفاده صحیح آسان و استفاده نادرست دشوار باشد — انواع قوی،
[[nodiscard]]، معنابخشی مقداری - قبل از بهینهسازی پروفایل کنید — از
perf، Instruments یا Google Benchmark برای یافتن گلوگاههای واقعی استفاده کنید - کد را از نظر مدیریت منابع، صحت const، ایمنی نخ و مدیریت صحیح خطا بازبینی کنید
- برای اشکالزدایی تولیدی آماده شوید: با نمادهای اشکالزدایی ارسال کنید، از ثبتوقایع ساختاریافته استفاده کنید، تخلیه هسته را فعال کنید
آزمون — دانش خود را بسنجید
(15 امتیاز)1. قانون ۵ در راهنمای اصلی C++ چه میگوید؟
2. قانون طلایی طراحی API در C++ چیست؟
3. چرا باید قبل از بهینهسازی پروفایل کنید؟