c++ - मेरे शामिल रक्षकों को रिकर्सिव समावेशन और एकाधिक प्रतीक परिभाषाओं को रोकने में शामिल क्यों नहीं हैं?
header-files c++-faq (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" संकलित करने में त्रुटियां क्यों मिलती हैं? मेरी समस्या को हल करने के लिए मुझे क्या करने की ज़रूरत है?
दूसरा सवाल:
कई परिभाषाओं को रोकने वाले गार्ड शामिल क्यों नहीं हैं? उदाहरण के लिए, जब मेरी प्रोजेक्ट में दो फाइलें होती हैं जिनमें एक ही शीर्षलेख शामिल होता है, तो कभी-कभी लिंकर कई प्रतीकों को कई बार परिभाषित करने के बारे में शिकायत करता है। उदाहरण के लिए:
"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 प्रति मानक:
एक # अंतर्निहित प्रीप्रोकैसिंग निर्देश एक स्रोत फ़ाइल में प्रकट हो सकता है जिसे किसी अन्य फ़ाइल में # अंतर्निहित निर्देश के कारण पढ़ा गया है, कार्यान्वयन-परिभाषित घोंसले सीमा तक ।
तो क्या चल रहा है ?
-
main.cpp
पार्स करते समय, प्रीप्रोसेसर निर्देश को पूरा करेगा#include "ah"
। यह निर्देश प्रीप्रोसेसर को हेडर फ़ाइलah
को संसाधित करने के लिए बताता है, उस प्रसंस्करण का परिणाम लेता है, और उस परिणाम के साथ स्ट्रिंग#include "ah"
को प्रतिस्थापित करता है; -
ah
संसाधित करते समय, प्रीप्रोसेसर निर्देश#include "bh"
पूरा#include "bh"
को पूरा करेगा, और एक ही तंत्र लागू होता है: प्रीप्रोसेसर हेडर फ़ाइलbh
को संसाधित करेगा, इसकी प्रसंस्करण का परिणाम लेगा, और उस परिणाम के साथ#include
अंतर्निहित निर्देश को प्रतिस्थापित करेगा; -
bh
प्रसंस्करण करते समय, निर्देश#include "ah"
प्रीप्रोसेसर कोah
प्रक्रिया करने के लिए बताएगा और परिणाम के साथ उस निर्देश को प्रतिस्थापित करेगा; - प्रीप्रोसेसर फिर से विश्लेषण करना शुरू कर देगा, फिर से
#include "bh"
निर्देश को पूरा करेगा, और यह एक संभावित अनंत रिकर्सिव प्रक्रिया स्थापित करेगा। महत्वपूर्ण घोंसले स्तर तक पहुंचने पर, कंपाइलर एक त्रुटि की रिपोर्ट करेगा।
जब गार्ड शामिल होते हैं , हालांकि, चरण 4 में कोई अनंत रिकर्सन स्थापित नहीं किया जाएगा। देखते हैं क्यों:
- ( पहले जैसा ही है )
main.cpp
पार्स करते समय, प्रीप्रोसेसर निर्देश को पूरा करेगा#include "ah"
। यह प्रीप्रोसेसर को हेडर फ़ाइलah
को संसाधित करने के लिए कहता है, उस प्रसंस्करण का परिणाम लें, और उस परिणाम के साथ स्ट्रिंग#include "ah"
को प्रतिस्थापित करें; -
ah
प्रसंस्करण करते समय, प्रीप्रोसेसर निर्देश#ifndef A_H
को पूरा करेगा। चूंकि मैक्रोA_H
अभी तक परिभाषित नहीं किया गया है, यह निम्न पाठ को संसाधित रखेगा। बाद के निर्देश (#defines A_H
) मैक्रो#defines A_H
परिभाषित करता है। फिर, प्रीप्रोसेसर निर्देश#include "bh"
पूरा करेगा#include "bh"
: प्रीप्रोसेसर अब हेडर फ़ाइलbh
संसाधित करेगा, इसकी प्रसंस्करण का परिणाम लें, और उस परिणाम के साथ#include
अंतर्निहित निर्देश को प्रतिस्थापित करें; -
bh
प्रसंस्करण करते समय, प्रीप्रोसेसर निर्देश#ifndef B_H
को पूरा करेगा। चूंकि मैक्रोB_H
अभी तक परिभाषित नहीं किया गया है, यह निम्न पाठ को संसाधित रखेगा। बाद के निर्देश (#defines B_H
) मैक्रोB_H
को परिभाषित करता है। फिर, निर्देश#include "ah"
preprocessor कोah
प्रक्रिया करने के लिए बताएगा और preprocessingah
के परिणाम के साथbh
में#include
निर्देश को प्रतिस्थापित करेगा; - कंपाइलर फिर से प्रीप्रोकैसिंग
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
अब निश्चित रूप से संकलित होगा। कुछ टिप्पणियां:
-
B
में आगे की घोषणा के साथ#include
निर्देश को प्रतिस्थापित करके पारस्परिक समावेश को तोड़ना न केवलB
पर निर्भरता व्यक्त करने के लिए पर्याप्त था: जब भी संभव हो, आगे की घोषणाओं का उपयोग करके / व्यावहारिक भी एक अच्छा प्रोग्रामिंग अभ्यास माना जाता है, क्योंकि इससे मदद मिलती है अनावश्यक समावेशन से परहेज, इस प्रकार समग्र संकलन समय को कम करना। हालांकि, पारस्परिक समावेश को समाप्त करने के बाद,main.cpp
को#include
और दोनों को#include
करने के लिए संशोधित किया जाना चाहिए (यदि बाद में इसकी आवश्यकता है), क्योंकिbh
अप्रत्यक्ष रूप से नहीं है#include
ah
माध्यम से#include
; - जबकि कक्षा
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
कीवर्ड को प्राथमिकता दी जानी चाहिए।