c++ - मेरे शामिल रक्षकों को रिकर्सिव समावेशन और एकाधिक प्रतीक परिभाषाओं को रोकने में शामिल क्यों नहीं हैं?



header-files c++-faq (1)

गार्ड शामिल करने के बारे में दो आम प्रश्न:

  1. पहला प्रश्न:

    पारदर्शी, रिकर्सिव समावेशन से मेरी हेडर फ़ाइलों की सुरक्षा करने वाले गार्ड शामिल क्यों नहीं हैं? मुझे गैर-मौजूदा प्रतीकों के बारे में त्रुटियां मिलती रहती हैं जो हर बार जब मैं कुछ लिखता हूं तो स्पष्ट रूप से वहां या यहां तक ​​कि वीर सिंटैक्स त्रुटियां भी होती हैं:

    "आह"

    #ifndef A_H
    #define A_H
    
    #include "b.h"
    
    ...
    
    #endif // A_H
    

    "Bh"

    #ifndef B_H
    #define B_H
    
    #include "a.h"
    
    ...
    
    #endif // B_H
    

    "Main.cpp"

    #include "a.h"
    int main()
    {
        ...
    }
    

    मुझे "main.cpp" संकलित करने में त्रुटियां क्यों मिलती हैं? मेरी समस्या को हल करने के लिए मुझे क्या करने की ज़रूरत है?

  1. दूसरा सवाल:

    कई परिभाषाओं को रोकने वाले गार्ड शामिल क्यों नहीं हैं? उदाहरण के लिए, जब मेरी प्रोजेक्ट में दो फाइलें होती हैं जिनमें एक ही शीर्षलेख शामिल होता है, तो कभी-कभी लिंकर कई प्रतीकों को कई बार परिभाषित करने के बारे में शिकायत करता है। उदाहरण के लिए:

    "Header.h"

    #ifndef HEADER_H
    #define HEADER_H
    
    int f()
    {
        return 0;
    }
    
    #endif // HEADER_H
    

    "Source1.cpp"

    #include "header.h"
    ...
    

    "Source2.cpp"

    #include "header.h"
    ...
    

    ये क्यों हो रहा है? मेरी समस्या को हल करने के लिए मुझे क्या करने की ज़रूरत है?


पहला प्रश्न:

पारदर्शी, रिकर्सिव समावेशन से मेरी हेडर फ़ाइलों की सुरक्षा करने वाले गार्ड शामिल क्यों नहीं हैं?

वे हैं

जो वे मदद नहीं कर रहे हैं वे हैंडल समेत डेटा संरचनाओं की परिभाषाओं के बीच निर्भरता है । इसका अर्थ यह देखने के लिए, आइए बुनियादी परिदृश्य से शुरू करें और देखें कि गार्ड शामिल आपसी समावेशों के साथ क्यों मदद करते हैं।

मान लीजिए कि आपके पारस्परिक रूप से ah और bh हेडर फ़ाइलों में छोटी सामग्री है, यानी प्रश्न के पाठ से कोड अनुभागों में लंबवत खाली स्ट्रिंग के साथ प्रतिस्थापित किया गया है। इस स्थिति में, आपका main.cpp खुशी से संकलित होगा। और यह केवल गार्ड शामिल करने के लिए धन्यवाद!

यदि आप आश्वस्त नहीं हैं, तो उन्हें हटाने का प्रयास करें:

//================================================
// a.h

#include "b.h"

//================================================
// b.h

#include "a.h"

//================================================
// main.cpp
//
// Good luck getting this to compile...

#include "a.h"
int main()
{
    ...
}

आप देखेंगे कि संकलक गहराई सीमा तक पहुंचने पर विफलता की रिपोर्ट करेगा। यह सीमा कार्यान्वयन-विशिष्ट है। सी ++ 11 मानक के अनुच्छेद 16.2 / 6 प्रति मानक:

एक # अंतर्निहित प्रीप्रोकैसिंग निर्देश एक स्रोत फ़ाइल में प्रकट हो सकता है जिसे किसी अन्य फ़ाइल में # अंतर्निहित निर्देश के कारण पढ़ा गया है, कार्यान्वयन-परिभाषित घोंसले सीमा तक

तो क्या चल रहा है ?

  1. main.cpp पार्स करते समय, प्रीप्रोसेसर निर्देश को पूरा करेगा #include "ah" । यह निर्देश प्रीप्रोसेसर को हेडर फ़ाइल ah को संसाधित करने के लिए बताता है, उस प्रसंस्करण का परिणाम लेता है, और उस परिणाम के साथ स्ट्रिंग #include "ah" को प्रतिस्थापित करता है;
  2. ah संसाधित करते समय, प्रीप्रोसेसर निर्देश #include "bh" पूरा #include "bh" को पूरा करेगा, और एक ही तंत्र लागू होता है: प्रीप्रोसेसर हेडर फ़ाइल bh को संसाधित करेगा, इसकी प्रसंस्करण का परिणाम लेगा, और उस परिणाम के साथ #include अंतर्निहित निर्देश को प्रतिस्थापित करेगा;
  3. bh प्रसंस्करण करते समय, निर्देश #include "ah" प्रीप्रोसेसर को ah प्रक्रिया करने के लिए बताएगा और परिणाम के साथ उस निर्देश को प्रतिस्थापित करेगा;
  4. प्रीप्रोसेसर फिर से विश्लेषण करना शुरू कर देगा, फिर से #include "bh" निर्देश को पूरा करेगा, और यह एक संभावित अनंत रिकर्सिव प्रक्रिया स्थापित करेगा। महत्वपूर्ण घोंसले स्तर तक पहुंचने पर, कंपाइलर एक त्रुटि की रिपोर्ट करेगा।

जब गार्ड शामिल होते हैं , हालांकि, चरण 4 में कोई अनंत रिकर्सन स्थापित नहीं किया जाएगा। देखते हैं क्यों:

  1. ( पहले जैसा ही है ) main.cpp पार्स करते समय, प्रीप्रोसेसर निर्देश को पूरा करेगा #include "ah" । यह प्रीप्रोसेसर को हेडर फ़ाइल ah को संसाधित करने के लिए कहता है, उस प्रसंस्करण का परिणाम लें, और उस परिणाम के साथ स्ट्रिंग #include "ah" को प्रतिस्थापित करें;
  2. ah प्रसंस्करण करते समय, प्रीप्रोसेसर निर्देश #ifndef A_H को पूरा करेगा। चूंकि मैक्रो A_H अभी तक परिभाषित नहीं किया गया है, यह निम्न पाठ को संसाधित रखेगा। बाद के निर्देश ( #defines A_H ) मैक्रो #defines A_H परिभाषित करता है। फिर, प्रीप्रोसेसर निर्देश #include "bh" पूरा करेगा #include "bh" : प्रीप्रोसेसर अब हेडर फ़ाइल bh संसाधित करेगा, इसकी प्रसंस्करण का परिणाम लें, और उस परिणाम के साथ #include अंतर्निहित निर्देश को प्रतिस्थापित करें;
  3. bh प्रसंस्करण करते समय, प्रीप्रोसेसर निर्देश #ifndef B_H को पूरा करेगा। चूंकि मैक्रो B_H अभी तक परिभाषित नहीं किया गया है, यह निम्न पाठ को संसाधित रखेगा। बाद के निर्देश ( #defines B_H ) मैक्रो B_H को परिभाषित करता है। फिर, निर्देश #include "ah" preprocessor को ah प्रक्रिया करने के लिए बताएगा और preprocessing ah के परिणाम के साथ bh में #include निर्देश को प्रतिस्थापित करेगा;
  4. कंपाइलर फिर से प्रीप्रोकैसिंग ah शुरू कर देगा, और फिर से #ifndef A_H निर्देश को पूरा करेगा। हालांकि, पिछले प्रीप्रोकैसिंग के दौरान, मैक्रो A_H को परिभाषित किया गया है। इसलिए, संकलक इस समय निम्न पाठ को छोड़ देगा जब तक मिलान #endif निर्देश नहीं मिलता है, और इस प्रसंस्करण का आउटपुट खाली स्ट्रिंग है ( #endif कि कुछ भी #endif निर्देश का पालन नहीं करता है)। प्रीप्रोसेसर इसलिए खाली स्ट्रिंग के साथ bh में #include "ah" निर्देश को प्रतिस्थापित करेगा, और जब तक यह main.cpp में मूल #include main.cpp निर्देश को प्रतिस्थापित नहीं करता तब तक निष्पादन का पता main.cpp

इस प्रकार, गार्ड शामिल आपसी समावेश के खिलाफ सुरक्षा करते हैं । हालांकि, वे पारस्परिक रूप से आपकी कक्षाओं की परिभाषाओं के बीच निर्भरताओं में मदद नहीं कर सकते हैं- फाइलों सहित:

//================================================
// a.h

#ifndef A_H
#define A_H

#include "b.h"

struct A
{
};

#endif // A_H

//================================================
// b.h

#ifndef B_H
#define B_H

#include "a.h"

struct B
{
    A* pA;
};

#endif // B_H

//================================================
// main.cpp
//
// Good luck getting this to compile...

#include "a.h"
int main()
{
    ...
}

उपरोक्त शीर्षकों को देखते हुए, main.cpp संकलित नहीं होगा।

ये क्यों हो रहा है?

यह देखने के लिए कि क्या हो रहा है, चरण 1-4 से फिर से जाना पर्याप्त है।

यह देखना आसान है कि पहले तीन कदम और चौथे चरण में से अधिकांश इस परिवर्तन से अप्रभावित हैं (केवल विश्वास प्राप्त करने के लिए उनके माध्यम से पढ़ें)। हालांकि, चरण 4 के अंत में कुछ अलग होता है: खाली स्ट्रिंग के साथ B में #include "ah" निर्देश को बदलने के बाद, प्रीप्रोसेसर B की सामग्री को पार्स करना शुरू कर देगा, विशेष रूप से, B की परिभाषा। दुर्भाग्यवश, B की परिभाषा कक्षा A का उल्लेख करती है, जिसे समावेशन गार्ड के कारण बिल्कुल पहले कभी नहीं मिला है!

किसी प्रकार का सदस्य चर घोषित करना जिसे पहले घोषित नहीं किया गया है, निश्चित रूप से, एक त्रुटि है, और संकलक विनम्रता से इसे इंगित करेगा।

मेरी समस्या को हल करने के लिए मुझे क्या करने की ज़रूरत है?

आपको आगे की घोषणा की जरूरत है

वास्तव में, कक्षा B को परिभाषित करने के लिए कक्षा A की परिभाषा की आवश्यकता नहीं है, क्योंकि A को पॉइंटर को सदस्य चर के रूप में घोषित किया जा रहा है, और प्रकार A की वस्तु नहीं है। चूंकि पॉइंटर्स ने निश्चित आकार निर्धारित किया है, इसलिए संकलक को A के सटीक लेआउट को जानने की आवश्यकता नहीं होगी और न ही कक्षा B को सही तरीके से परिभाषित करने के लिए इसके आकार की गणना करने की आवश्यकता होगी। इसलिए, bh में कक्षा A घोषित करने और संकलक को इसके अस्तित्व के बारे में जागरूक करने के लिए पर्याप्त है:

//================================================
// b.h

#ifndef B_H
#define B_H

// Forward declaration of A: no need to #include "a.h"
struct A;

struct B
{
    A* pA;
};

#endif // B_H

आपका main.cpp अब निश्चित रूप से संकलित होगा। कुछ टिप्पणियां:

  1. B में आगे की घोषणा के साथ #include निर्देश को प्रतिस्थापित करके पारस्परिक समावेश को तोड़ना न केवल B पर निर्भरता व्यक्त करने के लिए पर्याप्त था: जब भी संभव हो, आगे की घोषणाओं का उपयोग करके / व्यावहारिक भी एक अच्छा प्रोग्रामिंग अभ्यास माना जाता है, क्योंकि इससे मदद मिलती है अनावश्यक समावेशन से परहेज, इस प्रकार समग्र संकलन समय को कम करना। हालांकि, पारस्परिक समावेश को समाप्त करने के बाद, main.cpp को #include और दोनों को #include करने के लिए संशोधित किया जाना चाहिए (यदि बाद में इसकी आवश्यकता है), क्योंकि bh अप्रत्यक्ष रूप से नहीं है #include ah माध्यम से #include ;
  2. जबकि कक्षा A की अग्रेषित घोषणा संकलक के लिए उस वर्ग के पॉइंटर्स घोषित करने के लिए पर्याप्त है (या किसी अन्य संदर्भ में इसका उपयोग करने के लिए जहां अपूर्ण प्रकार स्वीकार्य हैं), डी को संदर्भित करने के लिए पॉइंटर्स (उदाहरण के लिए सदस्य फ़ंक्शन का आह्वान करना) या इसकी गणना करना आकार अपूर्ण प्रकारों पर अवैध संचालन हैं: यदि इसकी आवश्यकता है, तो A की पूर्ण परिभाषा को संकलक के लिए उपलब्ध होना आवश्यक है, जिसका अर्थ है कि हेडर फ़ाइल जो इसे परिभाषित करती है उसे शामिल किया जाना चाहिए। यही कारण है कि कक्षा परिभाषाओं और उनके सदस्य कार्यों के कार्यान्वयन को आमतौर पर एक हेडर फ़ाइल में विभाजित किया जाता है और उस वर्ग के लिए एक कार्यान्वयन फ़ाइल (कक्षा टेम्पलेट्स इस नियम के लिए अपवाद हैं): कार्यान्वयन फ़ाइलें, जो कभी भी #include अन्य फ़ाइलों द्वारा #include नहीं होती हैं परियोजना, परिभाषाओं को दृश्यमान बनाने के लिए सुरक्षित रूप से #include सभी आवश्यक शीर्षकों को #include कर सकती है। दूसरी ओर, शीर्षलेख फ़ाइलें #include अन्य हेडर फ़ाइलों को #include नहीं करतीं जब तक कि उन्हें वास्तव में ऐसा करने की आवश्यकता न हो (उदाहरण के लिए, बेस क्लास की परिभाषा को परिभाषित करने के लिए), और जब भी संभव / व्यावहारिक हो, आगे की घोषणाओं का उपयोग करेंगे।

दूसरा सवाल:

कई परिभाषाओं को रोकने वाले गार्ड शामिल क्यों नहीं हैं?

वे हैं

वे आपकी सुरक्षा नहीं कर रहे हैं अलग-अलग अनुवाद इकाइयों में कई परिभाषाएं हैं। यह स्टैक ओवरफ्लो पर इस क्यू एंड ए में भी समझाया गया है।

इसे देखने के लिए, शामिल गार्ड को हटाने और source1.cpp (या source2.cpp संशोधित संस्करण को source1.cpp source2.cpp , जो इसके लिए महत्वपूर्ण है):

//================================================
// source1.cpp
//
// Good luck getting this to compile...

#include "header.h"
#include "header.h"

int main()
{
    ...
}

संकलक निश्चित रूप से f() को फिर से परिभाषित करने के बारे में शिकायत करेगा। यह स्पष्ट है: इसकी परिभाषा दो बार शामिल की जा रही है! हालांकि, उपर्युक्त source1.cpp समस्याओं के बिना संकलित करेगा जब source1.cpp में उचित शामिल गार्ड शामिल हैं । यह उम्मीद है।

फिर भी, यहां तक ​​कि जब गार्ड शामिल हैं और संकलक त्रुटि संदेश के साथ आपको परेशान करना बंद कर देगा, तो लिंकर इस तथ्य पर जोर देगा कि source1.cpp और source2.cpp के संकलन से प्राप्त ऑब्जेक्ट कोड विलय करते समय कई परिभाषाएं पाई जा रही हैं, और आपके निष्पादन योग्य उत्पन्न करने से इंकार कर देगा।

ये क्यों हो रहा है?

असल में, प्रत्येक परियोजना में आपकी .cpp फ़ाइल (इस संदर्भ में तकनीकी शब्द अनुवाद इकाई है ) अलग-अलग और स्वतंत्र रूप से संकलित की जाती है। एक .cpp फ़ाइल को पार्स करते समय, प्रीप्रोसेसर सभी #include अंतर्निहित निर्देशों को संसाधित करेगा और सभी मैक्रो इनोकेशन का मुकाबला करेगा, और इस शुद्ध पाठ प्रसंस्करण के आउटपुट को ऑब्जेक्ट कोड में अनुवाद करने के लिए कंपाइलर को इनपुट में दिया जाएगा। एक बार जब एक अनुवाद इकाई के लिए ऑब्जेक्ट कोड बनाने के साथ संकलक किया जाता है, तो यह अगले के साथ आगे बढ़ेगा, और पिछली अनुवाद इकाई को संसाधित करते समय सामने आने वाली सभी मैक्रो परिभाषाएं भुला दी जाएंगी।

वास्तव में, n अनुवाद इकाइयों ( .cpp फ़ाइलों) के साथ एक प्रोजेक्ट को संकलित करना एक ही प्रोग्राम (कंपाइलर) n बार निष्पादित करना है, हर बार एक अलग इनपुट के साथ: उसी प्रोग्राम के विभिन्न निष्पादन पिछले की स्थिति को साझा नहीं करेंगे कार्यक्रम निष्पादन (ओं) । इस प्रकार, प्रत्येक अनुवाद स्वतंत्र रूप से किया जाता है और एक अनुवाद इकाई को संकलित करते समय प्रीप्रोसेसर प्रतीकों को याद नहीं किया जाएगा जब अन्य अनुवाद इकाइयों को संकलित करते समय याद किया जाएगा (यदि आप इसे एक पल के बारे में सोचते हैं, तो आप आसानी से महसूस करेंगे कि यह वास्तव में एक वांछनीय व्यवहार है)।

इसलिए, भले ही गार्ड शामिल हों, आपको एक अनुवाद इकाई में एक ही शीर्षलेख के पुनरावर्ती आपसी समावेशन और अनावश्यक समावेशन को रोकने में मदद मिलती है, लेकिन वे यह नहीं पहचान सकते कि क्या एक ही परिभाषा अलग-अलग अनुवाद इकाई में शामिल है या नहीं।

फिर भी, जब आपके प्रोजेक्ट की सभी .cpp फ़ाइलों के संकलन से उत्पन्न ऑब्जेक्ट कोड विलय करते हैं, तो लिंकर देखेंगे कि एक ही प्रतीक को एक से अधिक बार परिभाषित किया गया है, और चूंकि यह एक परिभाषा नियम का उल्लंघन करता है। सी ++ 11 मानक के अनुच्छेद 3.2 / 3 प्रति मानक:

प्रत्येक कार्यक्रम में प्रत्येक गैर-इनलाइन फ़ंक्शन या वैरिएबल की बिल्कुल एक परिभाषा होगी जिसमें उस प्रोग्राम में ओडीआर-प्रयुक्त होता है; कोई निदान की आवश्यकता नहीं है। परिभाषा प्रोग्राम में स्पष्ट रूप से दिखाई दे सकती है, यह मानक या उपयोगकर्ता परिभाषित पुस्तकालय में पाया जा सकता है, या (जब उचित हो) इसे स्पष्ट रूप से परिभाषित किया गया है (12.1, 12.4 और 12.8 देखें)। एक इनलाइन फ़ंक्शन को प्रत्येक अनुवाद इकाई में परिभाषित किया जाएगा जिसमें यह ओडीआर-प्रयुक्त होता है

इसलिए, लिंकर एक त्रुटि उत्सर्जित करेगा और आपके प्रोग्राम के निष्पादन योग्य उत्पन्न करने से इंकार कर देगा।

मेरी समस्या को हल करने के लिए मुझे क्या करने की ज़रूरत है?

यदि आप अपनी फ़ंक्शन परिभाषा को हेडर फ़ाइल में रखना चाहते हैं जो एकाधिक अनुवाद इकाइयों द्वारा #include (नोटिस, कि कोई हेडर नहीं है यदि आपका हेडर #include केवल एक अनुवाद इकाई द्वारा #include ), तो आपको inline कीवर्ड का उपयोग करना होगा ।

अन्यथा, आपको केवल अपनी कार्यक्षमता (बॉडी) को एक अलग .cpp फ़ाइल में डालने के लिए header.h . header.h में अपने फ़ंक्शन की घोषणा रखना होगा (यह शास्त्रीय दृष्टिकोण है)।

inline कीवर्ड नियमित फ़ंक्शन कॉल के लिए स्टैक फ्रेम सेट करने के बजाय, सीधे कॉल साइट पर फ़ंक्शन के बॉडी को इनलाइन करने के लिए कंपाइलर को एक गैर-बाध्यकारी अनुरोध का प्रतिनिधित्व करता है। यद्यपि कंपाइलर को आपके अनुरोध को पूरा करने की आवश्यकता नहीं है, लेकिन inline कीवर्ड लिंकर को कई प्रतीकों की परिभाषाओं को सहन करने में कहने में सफल होता है। सी ++ 11 मानक के अनुच्छेद 3.2 / 5 के अनुसार:

कक्षा प्रकार (क्लॉज 9), गणना प्रकार (7.2), बाहरी लिंकेज (7.1.2), कक्षा टेम्पलेट (क्लॉज 14), गैर स्थैतिक फ़ंक्शन टेम्पलेट (14.5.6) के साथ इनलाइन फ़ंक्शन की एक से अधिक परिभाषा हो सकती है। , क्लास टेम्पलेट (14.5.1.3) का स्थिर डेटा सदस्य, क्लास टेम्पलेट (14.5.1.1) का सदस्य फ़ंक्शन, या टेम्पलेट विशेषज्ञता जिसके लिए कुछ टेम्पलेट पैरामीटर निर्दिष्ट नहीं हैं (14.7, 14.5.5) प्रत्येक प्रोग्राम में प्रदान किया गया है परिभाषा एक अलग अनुवाद इकाई में प्रकट होती है, और प्रदान की गई परिभाषा निम्नलिखित आवश्यकताओं को पूरा करती है [...]

उपर्युक्त पैराग्राफ मूल रूप से सभी परिभाषाओं को सूचीबद्ध करता है जिन्हें आमतौर पर हेडर फ़ाइलों में रखा जाता है , क्योंकि उन्हें कई अनुवाद इकाइयों में सुरक्षित रूप से शामिल किया जा सकता है। बाहरी लिंकेज के साथ अन्य सभी परिभाषाएं, इसके बजाय, स्रोत फ़ाइलों में हैं।

inline कीवर्ड की बजाय static कीवर्ड का उपयोग करने से आपके फ़ंक्शन आंतरिक लिंकेज को देकर लिंकर त्रुटियों को दबाने में भी परिणाम होता है, इस प्रकार प्रत्येक अनुवाद इकाई को उस फ़ंक्शन (और इसके स्थानीय स्थैतिक चर) की एक निजी प्रतिलिपि बनाते हैं। हालांकि, यह अंततः एक बड़े निष्पादन योग्य में परिणाम देता है, और सामान्य रूप से inline के उपयोग को प्राथमिकता दी जानी चाहिए।

static कीवर्ड के साथ एक ही परिणाम प्राप्त करने का एक वैकल्पिक तरीका एक अज्ञात नेमस्पेस में फ़ंक्शन f() डालना है। सी ++ 11 मानक के अनुच्छेद 3.5 / 4 मानक:

एक अनाम नामस्थान या एक नामांकित नामस्थान के भीतर प्रत्यक्ष या अप्रत्यक्ष रूप से घोषित नामस्थान में आंतरिक संबंध है। अन्य सभी नामस्थानों में बाहरी संबंध है। नामस्थान स्कोप वाले नाम को ऊपर दिए गए आंतरिक लिंकेज को नहीं दिया गया है, यदि नाम का नाम संलग्न नाम के समान लिंक है:

- एक परिवर्तनीय; या

- एक समारोह ; या

- एक नामित वर्ग (क्लॉज 9), या एक टाइपेडफ घोषणा में परिभाषित एक अज्ञात वर्ग जिसमें कक्षा को लिंक प्रयोजनों के लिए टाइपिफ़ नाम है (7.1.3); या

- एक नामांकित गणना (7.2), या एक टाइपेडफ घोषणा में परिभाषित एक अनाम गणना जिसमें गणना के उद्देश्यों के लिए टाइपिफ़ नाम है (7.1.3); या

- जुड़ाव के साथ एक गणना से संबंधित एक गणक; या

- टेम्पलेट।

ऊपर वर्णित एक ही कारण के लिए, inline कीवर्ड को प्राथमिकता दी जानी चाहिए।





include-guards