در C++، اعلانها اغلب از تعریفها جدا میشوند. اعلانها در بهاصطلاح فایلهای سرصفحه گروهبندی میشوند و پیادهسازیهای مربوطه در فایلهای منبع قرار میگیرند. میتوانید فایلهای سرصفحه را مانند یک API در نظر بگیرید. فایل سرصفحه به شما میگوید که یک پایگاه کد چه امکاناتی دارد، بدون اینکه وارد جزئیات چگونه آن شود.
رایجترین پسوند فایل برای فایلهای سرصفحه .h است.
برخی پروژهها از .hpp استفاده میکنند یا پسوند را کاملاً حذف میکنند.
تعریفها در یک فایل .cpp جداگانه قرار میگیرند.
برای پیوند دوبارهی این بخشها، فایل منبع با درج کردن فایل سرصفحهی مربوطه آغاز میشود.
اگر میخواهید کتابخانهای به اسم «quick_math» بنویسید که تابعی به اسم «super_root» ارائه میدهد و میخواهید از آن زیاد استفاده کنید، فایلها به این شکل خواهند بود:
// A file named quick_math.h
#pragma once
namespace quick_math {
double super_root(double x, int n);
}
// A file named quick_math.cpp
#include <cmath>
#include "quick_math.h"
double quick_math::super_root(double x, int n) {
while (n) {
x = std::sqrt(x), --n;
}
return x;
}
اگر لازم است سرصفحهای را درج کنید که فقط پیادهسازی به آن نیاز دارد، خط #include مربوطه فقط در فایل منبع لازم است.
هر چیزی که در سرصفحه درج شود، در فایل .cpp هم در دسترس است، مثل کتابخانهی string در مثال زیر.
توجه: علامت ; بعد از اعلان در فایل سرصفحه لازم است، اما بعد از تعریف در فایل منبع لازم نیست.
بسیاری از تمرینهای C++ در Exercism با دو فایل تقریباً خالی شروع میشوند: سرصفحه و منبع.
برای حل تمرین باید فایل *_test.cpp را بررسی کنید تا اسمها و فضاهای نام توابع مورد انتظار را ببینید.
کلاسها میتوانند بسیار پیچیده شوند و رابطهی آنها با تقسیمبندی سرصفحه و منبع ممکن است گیجکننده باشد. یک چیدمان ممکن این است که همهی جزئیات پیادهسازی را در فایل منبع و همهی اعلانها و متغیرهای عضو را در سرصفحه نگه دارید:
// A file named robot_flower.h
#if !defined(ROBOT_FLOWER_H)
#define ROBOT_FLOWER_H
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0);
void give_water();
std::string get_name();
int get_size();
void start_next_day();
};
} // namespace robots
#endif
// A file named robot_flower.cpp
#include "robot_flower.h"
robots::Flower::Flower(std::string name, int size) {
this->name = "Robotica " + name;
this->size = size;
}
void robots::Flower::start_next_day() {
if (!needs_water) ++size;
needs_water = true;
}
std::string robots::Flower::get_name() { return name; }
int robots::Flower::get_size() { return size; }
وقتی از سرصفحه به عنوان نمای کلی API استفاده میشود، همان جایی است که فرد برای اطلاعاتی مثل مقادیر پیشفرض نگاه میکند.
بنابراین مقدار پیشفرض پارامتر size در سازنده در سرصفحه مدیریت میشود، نه در پیادهسازی.
تعریفها در فایل منبع با فضای نام robots و نوع کلاس Flower پیشوند میگیرند.
گزینهی چیدمان دیگر یک کتابخانهی فقط سرصفحه است که اصلاً فایل .cpp ندارد:
// A file named robot_flower.h
#pragma once
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0) {
this->name = "Robotica " + name;
this->size = size;
}
void give_water() { needs_water = false; }
std::string get_name() { return name; }
int get_size() { return size; }
void start_next_day() {
if (!needs_water) ++size;
needs_water = true;
}
};
} // namespace robots
پروژهها ممکن است از ترکیب این چیدمانها استفاده کنند و بحثهای زیادی دربارهی اینکه کدامیک برای هر کاربرد مناسبتر است وجود دارد.
شاید خط #pragma once را در فایل سرصفحهی مثال بالا متوجه شده باشید.
به این یک محافظ درج میگویند، و تضمین میکند که محتوای فایل در هنگام کامپایل فقط یک بار درج شود تا از خطاها جلوگیری شود.
گونهی دیگری از محافظ درج هم وجود دارد که پیچیدهتر است و با #ifndef شروع و با #endif تمام میشود، که در ادامه توضیح داده شده است.
کد C++ به صورت رویهای ارزیابی میشود. اگر میخواهید از تابعی استفاده کنید، باید در لحظهی استفاده برای کامپایلر شناختهشده باشد. گاهی نمیتوان ترتیب خطیای برای چیدمان تعریفها در کد منبع پیدا کرد. به مثال زیر نگاه کنید:
int myFunction(int n) {
if (n < 10) {
return n;
} else {
return myOtherFunction(n / 10);
}
}
int myOtherFunction(int m) { return myFunction(m / 2); }
وقتی myFunction تعریف میشود، کامپایلر هنوز myOtherFunction را نمیشناسد.
متأسفانه مشکل ارجاع دایرهای با جابهجا کردن ترتیب حل نمیشود.
C++ اعلانهای پیشرو را ارائه میدهد تا کامپایلر پیش از تعریف شدن، از myFunction و myOtherFunction آگاه شود.
کامپایلر فرض میکند که تعریف در نقطهای بعدتر پس از اعلان خواهد آمد.
مثال بعدی نشان میدهد که چگونه یک اعلان پیشرو برای توابع استفاده میشود.
int myFunction(int n); // Forward declaration of myFunction
int myOtherFunction(int m); // Forward declaration of myOtherFunction
// Definition of myFunction
int myFunction(int n) {
if (n < 0) {
return 0;
} else {
return myOtherFunction(n - 2);
}
}
// Definition of myOtherFunction
int myOtherFunction(int m) { return myFunction(m / 2); }
ifndef
مهم نیست که یک فایل چند بار در یک پروژه درج شود. فایلهای سرصفحه نباید تعریف داشته باشند. یک پروژهی کامل نمیتواند یک تعریف را بیش از یک بار داشته باشد. به این «قاعدهی یک تعریف» میگویند. کامپایلر آن را اعمال میکند.
با محافظهای درج بهراحتی میتوان از درجهای ناخواستهی متعدد یک تعریف جلوگیری کرد.
آنها با روشی ویژه در مراحل کامپایل شکل میگیرند.
هدف این است که فایلی فقط در صورتی درج شود که متغیر خاصی تنظیم نشده باشد و سپس به محض درج فایل، آن متغیر تنظیم شود.
اغلب دنبالهی این متغیر به عنوان گونهای از اسم فایل انتخاب میشود.
روش دیگر تولید یک UUID است تا خطر استفادهی تصادفی دو بار از یک دنباله کاهش یابد.
نحوهی نگارش را میتوانید در زیر ببینید، با MY_HEADER_FILE_H به عنوان متغیر.
#ifndef MY_HEADER_FILE_H /* any name uniquely mapped to file name */
#define MY_HEADER_FILE_H
// file content
#endif
مشکل #pragma once این است که پراگماها بخش رسمی زبان C++ نیستند و پیادهسازی آنها از کامپایلری به کامپایلر دیگر متفاوت است.
بسیاری از پروژههای بزرگ به روش سادهتر پراگما روی آوردهاند، اما چند پروژه هنوز محتاط هستند.