c++ प्रत्यक्ष प्रत्यक्ष#किंकर्तव्यविमूढ़ बनाम गैर-संविदात्मक सकर्मक#किंकर्तव्यविमूढ़




header include (5)

कहें कि हमारे पास यह हेडर फ़ाइल है:

MyClass.hpp

#pragma once
#include <vector>

class MyClass
{
public:
    MyClass(double);

    /* ... */

private:
    std::vector<double> internal_values;
};

अब, जब भी हम किसी अन्य hpp या cpp फ़ाइल में #include "MyClass.hpp" MyClass.hpp #include "MyClass.hpp" उपयोग करते हैं, तो हम इस तथ्य के बावजूद भी प्रभावी रूप से #include <vector> करते हैं कि हमें इसकी आवश्यकता नहीं है। जिस कारण से मैं कह रहा हूं कि इसकी आवश्यकता नहीं है, यह है कि std::vector का उपयोग केवल MyClass में आंतरिक रूप से किया जाता है, लेकिन वास्तव में इस वर्ग के साथ बातचीत करने के लिए यह बिल्कुल भी आवश्यक नहीं है।

नतीजतन, मैं लिख सकता था

संस्करण 1: SomeOtherHeader.hpp

#pragma once
#include "MyClass.hpp"

void func(const MyClass&, const std::vector<double>&);

जबकि मुझे शायद लिखना चाहिए

संस्करण 2: SomeOtherHeader.hpp

#pragma once
#include "MyClass.hpp"
#include <vector>

void func(const MyClass&, const std::vector<double>&);

MyClass के आंतरिक कामकाज पर निर्भरता को रोकने के लिए। या मुझे करना चाहिए?

मुझे स्पष्ट रूप से पता है कि MyClass को काम करने के लिए <vector> आवश्यकता है। तो यह एक दार्शनिक सवाल हो सकता है। लेकिन यह तय करना अच्छा नहीं होगा कि आयात करते समय कौन से हेडर सामने आते हैं (यानी नामस्थान में लोड हो जाता है)? ताकि प्रत्येक हेडर को #include करने की आवश्यकता हो, उसे क्या चाहिए, बिना किसी निहितार्थ के दूर होने पर, जिसमें किसी अन्य हेडर को चेन की आवश्यकता हो?

हो सकता है कि लोग आगामी C ++ 20 मॉड्यूल पर कुछ प्रकाश डाल सकें, जो मुझे विश्वास है कि इस मुद्दे के कुछ पहलुओं को संबोधित करेंगे।


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

यह भी आमतौर पर माना जाता है कि क्लीनर आपके आश्रितों को तुरंत वहां पहुंचा देता है। यदि आप यह जांचना चाहते हैं कि यह "MyClass" ऑब्जेक्ट क्या है, तो आप बस शीर्ष पर स्क्रॉल करना चाहते हैं और अपनी IDE को संबंधित शीर्ष लेख पर ले जाने के लिए कहते हैं।

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

सभी जो सभी के साथ सहमत थे, मैंने इसे फिर से पढ़ा है और मुझे नहीं लगता कि आपका प्रश्न वास्तव में "क्या मुझे ऐसा करना चाहिए?" इतना के रूप में "क्यों मैं भी ऐसा नहीं करने की अनुमति दी है?" या "क्यों संकलक मुझे शामिल नहीं करता है मेरे पास से 'शामिल हैं?"

"आप जो उपयोग करते हैं उसे सीधे शामिल करें" नियम का एक महत्वपूर्ण अपवाद है। वह हेडर है जो उनके विनिर्देशन के भाग के रूप में अतिरिक्त हेडर शामिल करता है । उदाहरण के लिए < iostream > (जो कि मानक पुस्तकालय का ही हिस्सा है) को <istream> और <ostream> शामिल करने के लिए c ++ 11 के रूप में गारंटी दी गई है। कोई कह सकता है कि "केवल <istream> और <ostream> की सामग्री को सीधे <iostream> में स्थानांतरित क्यों नहीं किया गया?" लेकिन स्पष्टता और संकलन गति के फायदे हैं कि उन्हें विभाजित करने का विकल्प अगर केवल एक की जरूरत है। (और, सी ++ के लिए कोई संदेह नहीं है, ऐतिहासिक कारण भी हैं) आप निश्चित रूप से अपने स्वयं के हेडर के लिए भी ऐसा कर सकते हैं। (यह एक ऑब्जेक्टिव-सी चीज़ से अधिक है, लेकिन उनमें वही शामिल हैं जो यांत्रिकी और पारंपरिक रूप से छतरी हेडर के लिए उनका उपयोग करते हैं, जिसका एकमात्र काम अन्य फ़ाइलों को शामिल करना है।)

एक और मौलिक कारण है कि आपके शामिल हेडर में शामिल शामिल हैं। यही है, सामान्य तौर पर, आपके हेडर उनके बिना कोई मतलब नहीं रखते हैं। मान लीजिए कि आपकी MyClass.hpp फ़ाइल में निम्न प्रकार का समानार्थी शब्द है

using NumberPack = std::vector<unsigned int>;

और निम्नलिखित स्व-वर्णनात्मक फ़ंक्शन

NumberPack getFirstTenNumers();

अब मान लीजिए कि एक अन्य फ़ाइल में MyClass.hpp शामिल है और निम्नलिखित है।

NumberPack counter = getFirstTenNumbers();
for (auto c : counter) {
    std::cout << c << "\n"
}

यहाँ क्या हो रहा है कि आप अपने कोड में नहीं लिखना चाहते हैं कि आप <vector> का उपयोग कर रहे हैं। यह एक कार्यान्वयन विवरण है जिसके बारे में आपको चिंता करने की आवश्यकता नहीं है। NumberPack , जहाँ तक आप चिंतित हैं, कुछ अन्य कंटेनर या एक इट्रेटर या एक जनरेटर प्रकार की चीज़ या कुछ और के रूप में कार्यान्वित किया जा सकता है, जब तक कि यह अपनी कल्पना का पालन नहीं करता है। लेकिन संकलक को यह जानना होगा कि वास्तव में यह क्या है: यह माता-पिता की निर्भरता का प्रभावी उपयोग नहीं कर सकता है बिना यह जाने कि दादा-दादी पर निर्भरता प्रमुख हैं। इसका एक साइड इफेक्ट यह है कि आप इनका इस्तेमाल करने से दूर हो जाते हैं।

या, ज़ाहिर है, तीसरा कारण सिर्फ "क्योंकि यह सी ++ नहीं है।" हां, एक ऐसी भाषा हो सकती है जिसमें दूसरी पीढ़ी की निर्भरताएं पास न हों, या आपको इसे स्पष्ट रूप से अनुरोध करना होगा। यह सिर्फ इतना है कि यह एक अलग भाषा होगी, और विशेष रूप से पुराने पाठ में फिट नहीं होगी, इसमें c ++ या दोस्तों की आधारित शैली शामिल है।


MyClass के आंतरिक कामकाज पर निर्भरता को रोकने के लिए। या मुझे करना चाहिए?

हाँ, आपको उस कारण के लिए और बहुत कुछ चाहिए। जब तक आप यह निर्दिष्ट नहीं करना चाहते कि MyClass.hpp को <vector> शामिल करने की गारंटी है, तो आप दूसरे सहित एक पर भरोसा नहीं कर सकते। और ऐसी गारंटी देने के लिए मजबूर होने का कोई अच्छा कारण नहीं है। यदि ऐसी कोई गारंटी नहीं है, तो आप MyClass.hpp के कार्यान्वयन विवरण पर भरोसा करते हैं जो भविष्य में बदल सकता है, जो आपके कोड को तोड़ देगा।

मुझे स्पष्ट रूप से पता है कि MyClass को काम करने के लिए वेक्टर की आवश्यकता है।

क्या यह? उदाहरण के लिए इसे boost::container::small_vector लिए उपयोग नहीं किया जा सकता है boost::container::small_vector इसके बजाय?

इस उदाहरण में MyClass को std :: वेक्टर की आवश्यकता है

लेकिन भविष्य में MyClass की जरूरतों के बारे में क्या? कार्यक्रम विकसित होते हैं, और आज जिस वर्ग की आवश्यकता होती है वह हमेशा वैसा नहीं होता है जैसा कि कल को कक्षा की आवश्यकता होती है।

लेकिन यह तय करना अच्छा नहीं होगा कि आयात करते समय कौन से हेडर सामने आते हैं

सकर्मक समावेश को रोकना संभव नहीं है।

C ++ 20 में पेश किए गए मॉड्यूल एक ऐसी विशेषता है जिसका उपयोग pp-समावेशन के बजाय किया जा सकता है और इसे हल करने में मदद करने के लिए अभिप्रेत है।

अभी, आप PIMPL पैटर्न ("पॉइंटर टू कार्यान्वयन") का उपयोग करके किसी भी कार्यान्वयन विवरण निर्भरता को शामिल करने से बच सकते हैं। लेकिन PIMPL अप्रत्यक्ष रूप से और अधिक महत्वपूर्ण रूप से एक परत का परिचय देता है, इसके लिए गतिशील आवंटन की आवश्यकता होती है जिसमें प्रदर्शन निहितार्थ होते हैं। संदर्भ के आधार पर, ये निहितार्थ नगण्य या महत्वपूर्ण हो सकते हैं।


विचार करें कि कोड को केवल एक बार लिखा जाना नहीं है, बल्कि यह समय के साथ विकसित होता है।

मान लेते हैं कि आपने कोड लिखा है और अब मेरा काम इसे रिफलेक्टर करना होगा। किसी कारण से मैं MyClass को YourClass से बदलना चाहता YourClass और कहता है कि उनके पास एक ही इंटरफ़ेस है। मुझे बस इस पर आने के लिए MyClass की किसी भी घटना को YourClass साथ YourClass होगा:

/* Version 1: SomeOtherHeader.hpp */

#pragma once
#include "YourClass.hpp"

void func(const YourClass& a, const std::vector<double>& b);

मैंने सब कुछ सही किया, लेकिन फिर भी कोड संकलित करने में विफल होगा (क्योंकि YourClass std::vector सहित नहीं है)। इस विशेष उदाहरण में मुझे एक स्पष्ट त्रुटि संदेश मिलेगा और फिक्स स्पष्ट होगा। हालाँकि, चीजें बहुत तेजी से गड़बड़ हो सकती हैं अगर ऐसी निर्भरता कई हेडर में फैले, अगर ऐसी कई निर्भरताएं हैं और अगर SomeOtherHeader.hpp में केवल एक ही घोषणा से अधिक है।

और भी चीजें हैं जो गलत हो सकती हैं। उदाहरण के लिए MyClass के लेखक ने फैसला किया कि वे वास्तव में एक आगे की घोषणा के पक्ष में शामिल ड्रॉप कर सकते हैं। इसके अलावा SomeOtherHeader टूट जाएगा। यह उबलता है: यदि आप SomeOtherHeader में vector को शामिल नहीं करते हैं तो एक छिपी निर्भरता है, जो खराब है।

ऐसी समस्याओं को रोकने के लिए अंगूठे का नियम है: जो भी आप उपयोग करते हैं उसे शामिल करें।


हां, उपयोग करने वाली फ़ाइल में स्पष्ट रूप से <vector> शामिल होना चाहिए , क्योंकि यह एक निर्भरता है जिसकी आवश्यकता है।

हालांकि, मैं झल्लाहट नहीं होगा। यदि कोई व्यक्ति <vector> को हटाने के लिए MyClass.hpp को MyClass.hpp करता है, तो संकलक उन्हें हर एक फ़ाइल पर इंगित करेगा जिसमें स्पष्ट <vector> शामिल नहीं था, जिसमें निहित पर भरोसा करना शामिल है। इस प्रकार की त्रुटियों को ठीक करने के लिए आम तौर पर यह नो-ब्रेनर होता है, और एक बार कोड फिर से संकलित होने के बाद, गायब हुए कुछ स्पष्ट शब्दों को शामिल कर लिया जाएगा।

अंत में, संकलक गायब होने में बहुत अधिक कुशल है, इसमें किसी भी इंसान की तुलना में शामिल है।


यदि आपके MyClass में std::vector<double> टाइप का सदस्य है, तो MyClass को परिभाषित करने वाले हेडर को #include <vector> को #include <vector> । अन्यथा, MyClass उपयोगकर्ताओं को संकलन करने का एकमात्र तरीका है यदि वे MyClass की परिभाषा को शामिल करने से पहले #include <vector>

हालांकि सदस्य private , यह अभी भी कक्षा का हिस्सा है, इसलिए कंपाइलर को एक पूर्ण प्रकार की परिभाषा देखने की जरूरत है। अन्यथा, यह गणना sizeof(MyClass) जैसी चीजों को नहीं कर सकता है, या किसी भी MyClass ऑब्जेक्ट को MyClass है।

यदि आप अपने हेडर और <vector> बीच निर्भरता को तोड़ना चाहते हैं तो तकनीकें हैं। उदाहरण के लिए, दाना ("कार्यान्वयन के लिए सूचक") मुहावरा।

class MyClass 
{
public:
    MyClass(double first_value);

    /* ... */

private:
    void *pimpl;
};

और, स्रोत फ़ाइल में जो कक्षा के सदस्यों को परिभाषित करता है;

#include <vector>
#include "MyClass.hpp"

MyClass::MyClass(double first_value) : pimpl(new std::vector<double>())
{

}

(और यह भी, संभवतया, first_value साथ कुछ करें, लेकिन मैंने इसे छोड़ दिया है)।

ट्रेडऑफ़ यह है कि वेक्टर का उपयोग करने की आवश्यकता वाले प्रत्येक सदस्य फ़ंक्शन को इसे pimpl से प्राप्त करने की आवश्यकता होती है। उदाहरण के लिए, यदि आप आवंटित वेक्टर का संदर्भ प्राप्त करना चाहते हैं

void MyClass::some_member_function()
{
    std::vector<double> &internal_data = *static_cast<std::vector<double> *>(pimpl);

}

MyClass विध्वंसक को गतिशील रूप से आवंटित वेक्टर को जारी करने की भी आवश्यकता होगी।

यह वर्ग परिभाषा के लिए कुछ विकल्पों को भी सीमित करता है। उदाहरण के लिए, MyClass में एक सदस्य फ़ंक्शन नहीं हो सकता है जो एक std::vector<double> देता है std::vector<double> मूल्य द्वारा (जब तक आप #include <vector> ) नहीं

आपको यह तय करने की आवश्यकता होगी कि क्या pimpl मुहावरे जैसी तकनीक आपके वर्ग के काम करने के प्रयास के लायक हैं। व्यक्तिगत रूप से, जब तक पिंपल मुहावरे का उपयोग करके कक्षा के कार्यान्वयन को अलग करने के लिए कुछ अन्य सम्मोहक कारण नहीं हैं, मैं आपकी हेडर फ़ाइल में केवल #include <vector> की आवश्यकता को स्वीकार करूंगा।







c++17