हे

हेडर में C++

2 अभ्यास

हेडर के बारे में

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 लाइब्रेरी। ध्यान दें: हेडर फाइल में घोषणा के बाद ; लगाना ज़रूरी है, लेकिन सोर्स फाइल में परिभाषा के बाद नहीं।

Note

Exercism पर बहुत से C++ अभ्यास दो लगभग खाली फाइलों से शुरू होते हैं: हेडर और सोर्स। अभ्यास हल करने के लिए आपको *_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 पर खत्म होता है। इसका ब्यौरा नीचे दिया गया है।

Advanced

फॉरवर्ड डिक्लेरेशन

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++ भाषा का आधिकारिक हिस्सा नहीं हैं, और इनका कार्यान्वयन हर कंपाइलर में अलग होता है। बहुत से बड़े प्रोजेक्ट सरल प्रैग्मा तरीके पर आ गए हैं, लेकिन कुछ अब भी सतर्क हैं।

GitHub के ज़रिए संपादित करें यह लिंक नई विंडो या टैब में खुलता है।

हेडर सीखिए