C ++ बड़े टेम्प्लेट के लिए कार्यान्वयन से निपटने का पसंदीदा तरीका


10

आमतौर पर सी ++ क्लास की घोषणा करते समय, हेडर फाइल में केवल घोषणा को लागू करना और कार्यान्वयन को एक स्रोत फ़ाइल में रखना सबसे अच्छा अभ्यास है। हालांकि, ऐसा लगता है कि यह डिज़ाइन मॉडल टेम्पलेट कक्षाओं के लिए काम नहीं करता है।

जब ऑनलाइन देखते हैं तो टेम्पलेट कक्षाओं को प्रबंधित करने के सर्वोत्तम तरीके पर 2 राय होती है:

1. शीर्ष लेख में घोषणा और कार्यान्वयन।

यह काफी सीधा है, लेकिन मेरी राय में, टेम्पलेट बड़ी होने पर कोड फ़ाइलों को बनाए रखने और संपादित करने में मुश्किल होती है।

2. एक टेम्प्लेट में कार्यान्वयन को शामिल करें फ़ाइल (.tpp) अंत में शामिल है।

यह मेरे लिए एक बेहतर समाधान की तरह लगता है लेकिन व्यापक रूप से लागू नहीं होता है। क्या कोई कारण है कि यह दृष्टिकोण हीन है?

मुझे पता है कि कई बार कोड की शैली व्यक्तिगत पसंद या विरासत शैली द्वारा निर्धारित होती है। मैं एक नई परियोजना शुरू कर रहा हूं (एक पुरानी सी परियोजना को सी ++ में पोर्ट कर रहा हूं) और मैं ओओ डिजाइन के लिए अपेक्षाकृत नया हूं और शुरू से ही सर्वोत्तम प्रथाओं का पालन करना चाहूंगा।


1
यह 9 साल पुराना लेख देखें codeproject.com पर। विधि 3 जो आपने वर्णित है। इतना खास नहीं लगता जितना आप मानते हैं।
डॉक ब्राउन

.. या यहाँ, उसी दृष्टिकोण, 2014 से लेख: codeofhonour.blogspot.com/2014/11/…
Doc Brown

2
बारीकी से संबंधित: stackoverflow.com/q/1208028/179910 । ग्नू आमतौर पर ".tpp" के बजाय ".tcc" एक्सटेंशन का उपयोग करता है, लेकिन यह अन्यथा बहुत अधिक समान है।
जेरी कॉफिन

मैंने हमेशा एक्सटेंशन के रूप में "ipp" का उपयोग किया, लेकिन मैंने जो कोड लिखा था, उसमें मैंने वही किया।
सेबेस्टियन रेडल जूल

जवाबों:


6

एक अस्थायी C ++ वर्ग लिखते समय, आपके पास आमतौर पर तीन विकल्प होते हैं:

(1) हेडर में घोषणा और परिभाषा रखें।

// foo.h
#pragma once

template <typename T>
struct Foo
{
    void f()
    {
        ...
    }
};

या

// foo.h
#pragma once

template <typename T>
struct Foo
{
    void f();
};

template <typename T>
inline void Foo::f()
{
    ...
}

समर्थक:

  • बहुत सुविधाजनक उपयोग (बस हेडर शामिल करें)।

कोन:

  • इंटरफ़ेस और विधि कार्यान्वयन मिश्रित हैं। यह "सिर्फ" एक पठनीयता समस्या है। कुछ को यह अप्राप्य लगता है, क्योंकि यह सामान्य से अलग है ।h / .cpp दृष्टिकोण। हालाँकि, ध्यान रखें कि यह अन्य भाषाओं में कोई समस्या नहीं है, उदाहरण के लिए, C # और Java।
  • उच्च पुनर्निर्माण प्रभाव: यदि आप Fooसदस्य के रूप में एक नए वर्ग की घोषणा करते हैं , तो आपको शामिल करने की आवश्यकता है foo.h। इसका अर्थ है कि Foo::fहेडर और स्रोत फ़ाइलों दोनों के माध्यम से प्रचार के कार्यान्वयन को बदलना ।

पुनर्निर्माण प्रभाव पर एक करीब से नज़र डालते हैं: गैर-अस्थायी सी ++ कक्षाओं के लिए, आप .hpp में .h और विधि परिभाषाएँ घोषित करते हैं। इस तरह, जब किसी पद्धति के कार्यान्वयन को बदल दिया जाता है, केवल एक .cpp को फिर से शुरू करने की आवश्यकता होती है। यह टेम्पलेट कक्षाओं के लिए अलग है। यदि आपके पास सभी कोड हैं। निम्नलिखित उदाहरण पर एक नज़र डालें:

// bar.h
#pragma once
#include "foo.h"
struct Bar
{
    void b();
    Foo<int> foo;
};

// bar.cpp
#include "bar.h"
void Bar::b()
{
    foo.f();
}

// qux.h
#pragma once
#include "bar.h"
struct Qux
{
    void q();
    Bar bar;
}

// qux.cpp
#include "qux.h"
void Qux::q()
{
    bar.b();
}

यहां, केवल उपयोग Foo::fअंदर है bar.cpp। हालांकि, अगर आप के कार्यान्वयन को बदलने Foo::f, दोनों bar.cppऔर qux.cppजरूरत कंपाइल किया जाना है। Foo::fदोनों फ़ाइलों में जीवन का कार्यान्वयन , भले ही Quxसीधे किसी भी हिस्से का उपयोग नहीं करता है Foo::f। बड़ी परियोजनाओं के लिए, यह जल्द ही एक समस्या बन सकती है।

(2) .h में घोषणापत्र डालें और .tpp में परिभाषा करें और इसे .h में शामिल करें।

// foo.h
#pragma once
template <typename T>
struct Foo
{
    void f();
};
#include "foo.tpp"    

// foo.tpp
#pragma once // not necessary if foo.h is the only one that includes this file
template <typename T>
inline void Foo::f()
{
    ...
}

समर्थक:

  • बहुत सुविधाजनक उपयोग (बस हेडर शामिल करें)।
  • इंटरफ़ेस और विधि की परिभाषाएँ अलग हो गई हैं।

कोन:

  • उच्च पुनर्निर्माण प्रभाव (उसी के रूप में (1) )।

यह समाधान .h / .cpp की तरह ही दो अलग-अलग फाइलों में घोषणा और विधि की परिभाषा को अलग करता है। हालांकि, इस दृष्टिकोण में (1) के रूप में एक ही पुनर्निर्माण समस्या है , क्योंकि हेडर में सीधे विधि परिभाषाएं शामिल हैं।

(3) .hpp में .h और परिभाषा में घोषणा डालें, लेकिन .hpp में .hpp को शामिल न करें।

// foo.h
#pragma once
template <typename T>
struct Foo
{
    void f();
};

// foo.tpp
#pragma once
template <typename T>
void Foo::f()
{
    ...
}

समर्थक:

  • .H / .cpp जुदाई के रूप में प्रभाव को कम करता है।
  • इंटरफ़ेस और विधि की परिभाषाएँ अलग हो गई हैं।

कोन:

  • असुविधाजनक उपयोग: Fooएक वर्ग में एक सदस्य को जोड़ते समय Bar, आपको foo.hहेडर में शामिल करने की आवश्यकता होती है । यदि आप Foo::fएक .cpp में कॉल करते हैं , तो आपको वहां भी शामिल करना foo.tppहोगा।

यह दृष्टिकोण पुनर्निर्माण प्रभाव को कम करता है, केवल .cpp फ़ाइलों के लिए जो वास्तव में उपयोग किए जाने की Foo::fआवश्यकता होती है उन्हें पुन: कंपाइल किया जाता है। हालांकि, यह एक मूल्य पर आता है: उन सभी फ़ाइलों को शामिल करने की आवश्यकता है foo.tpp। ऊपर से उदाहरण लें और नए दृष्टिकोण का उपयोग करें:

// bar.h
#pragma once
#include "foo.h"
struct Bar
{
    void b();
    Foo<int> foo;
};

// bar.cpp
#include "bar.h"
#include "foo.tpp"
void Bar::b()
{
    foo.f();
}

// qux.h
#pragma once
#include "bar.h"
struct Qux
{
    void q();
    Bar bar;
}

// qux.cpp
#include "qux.h"
void Qux::q()
{
    bar.b();
}

जैसा कि आप देख सकते हैं, एकमात्र अंतर इसमें शामिल अतिरिक्त foo.tppहै bar.cpp। यह असुविधाजनक है और एक वर्ग के लिए एक दूसरे को शामिल करना इस बात पर निर्भर करता है कि क्या आप उस पर कॉल करने के तरीके बहुत बदसूरत लगते हैं। हालांकि, आप पुनर्निर्माण प्रभाव को कम करते हैं: bar.cppयदि आप के कार्यान्वयन को बदलते हैं, तो केवल पुनर्नवीनीकरण की आवश्यकता है Foo::f। फ़ाइल qux.cppको कोई पुनर्संयोजन की आवश्यकता है।

सारांश:

यदि आप एक पुस्तकालय लागू करते हैं, तो आपको आमतौर पर प्रभाव के पुनर्निर्माण के बारे में परवाह करने की आवश्यकता नहीं है। आपकी लाइब्रेरी के उपयोगकर्ता किसी रिलीज़ को हड़प लेते हैं और उसका उपयोग करते हैं और लाइब्रेरी कार्यान्वयन उपयोगकर्ता के दिन-प्रतिदिन के काम में नहीं बदलता है। ऐसे मामलों में, पुस्तकालय दृष्टिकोण (1) या (2) का उपयोग कर सकता है और यह केवल स्वाद का मामला है जिसे आप चुनते हैं।

हालाँकि, यदि आप किसी एप्लिकेशन पर काम कर रहे हैं, या यदि आप अपनी कंपनी की आंतरिक लाइब्रेरी पर काम कर रहे हैं, तो कोड अक्सर बदलता रहता है। इसलिए आपको प्रभाव के पुनर्निर्माण के बारे में ध्यान रखना होगा। यदि आप अपने डेवलपर्स को अतिरिक्त शामिल करने के लिए स्वीकार करते हैं, तो दृष्टिकोण (3) चुनना एक अच्छा विकल्प हो सकता है।


2

.tppविचार के समान (जो मैंने कभी देखा नहीं है), हम एक -inl.hppफ़ाइल में सबसे अधिक इनलाइन कार्यक्षमता डालते हैं जो सामान्य .hppफ़ाइल के अंत में शामिल होती है ।

अन्य के संकेत के रूप में, यह एक अन्य फ़ाइल में इनलाइन कार्यान्वयन (जैसे टेम्पलेट) के अव्यवस्था को स्थानांतरित करके इंटरफ़ेस को पढ़ने योग्य रखता है। हम कुछ इंटरफ़ेस इनलाइन की अनुमति देते हैं, लेकिन उन्हें छोटे, आमतौर पर सिंगल लाइन फ़ंक्शन तक सीमित करने का प्रयास करते हैं।


1

2 संस्करण का एक समर्थक सिक्का यह है कि आपके हेडर अधिक सुव्यवस्थित दिखते हैं।

चुनाव हो सकता है कि आपके पास इनलाइन आईडीई त्रुटि जाँच हो, और डिबगर बाइंडिंग खराब हो जाए।


2 में भी बहुत सारे टेम्प्लेट पैरामीटर घोषणा अतिरेक की आवश्यकता होती है, जो विशेष रूप से sfinae का उपयोग करते समय बहुत क्रिया बन सकता है। और ओपी के विपरीत मुझे लगता है कि अधिक कोड को पढ़ने के लिए दूसरा कठिन है, विशेष रूप से अनावश्यक बॉयलर के कारण।
सोपेल

0

मैं कार्यान्वयन को एक अलग फ़ाइल में डालने के दृष्टिकोण को पसंद करता हूं, और हेडर फ़ाइल में सिर्फ प्रलेखन और घोषणाएँ करता हूं।

शायद इसका कारण जो आपने इस दृष्टिकोण को व्यवहार में नहीं देखा है, क्या आपने सही स्थानों में नहीं देखा है? ;-)

या - शायद इसकी क्योंकि यह सॉफ्टवेयर को विकसित करने में थोड़ा अतिरिक्त प्रयास करता है। लेकिन एक क्लास लाइब्रेरी के लिए, वह प्रयास, IMHO के समय के लायक है, और लाइब्रेरी का उपयोग / पढ़ने के लिए बहुत आसान में खुद के लिए भुगतान करता है।

उदाहरण के लिए इस लाइब्रेरी को लें: https://github.com/SophistSolutions/Stroika/

संपूर्ण पुस्तकालय इस दृष्टिकोण के साथ लिखा गया है और यदि आप कोड के माध्यम से देखते हैं, तो आप देखेंगे कि यह कितनी अच्छी तरह काम करता है।

हेडर फाइलें कार्यान्वयन फ़ाइलों के रूप में लंबे समय तक होती हैं, लेकिन वे घोषणाओं और प्रलेखन के अलावा कुछ भी नहीं भरते हैं।

अपने पसंदीदा std c ++ कार्यान्वयन (gcc या libc ++ या msvc) के साथ Stroika की पठनीयता की तुलना करें। वे सभी इनलाइन-हेडर कार्यान्वयन दृष्टिकोण का उपयोग करते हैं, और हालांकि वे बहुत अच्छी तरह से लिखे गए हैं, आईएमएचओ, पठनीय कार्यान्वयन के रूप में नहीं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.