2012-06-27 16 views
23

से प्राप्त करने से रोकें मैं समस्या का वर्णन करने के लिए उचित प्रश्न शीर्षक के बारे में नहीं सोच सकता। उम्मीद है कि नीचे दिए गए विवरण मेरी समस्या को स्पष्ट करते हैं।उपयोगकर्ता को गलत सीआरटीपी बेस

निम्नलिखित कोड

#include <iostream> 

template <typename Derived> 
class Base 
{ 
    public : 

    void call() 
    { 
     static_cast<Derived *>(this)->call_impl(); 
    } 
}; 

class D1 : public Base<D1> 
{ 
    public : 

    void call_impl() 
    { 
     data_ = 100; 
     std::cout << data_ << std::endl; 
    } 

    private : 

    int data_; 
}; 

class D2 : public Base<D1> // This is wrong by intension 
{ 
    public : 

    void call_impl() 
    { 
     std::cout << data_ << std::endl; 
    } 

    private : 

    int data_; 
}; 

int main() 
{ 
    D2 d2; 
    d2.call_impl(); 
    d2.call(); 
    d2.call_impl(); 
} 

यह संकलन और चलाने हालांकि D2 की परिभाषा जानबूझकर गलत है पर विचार करें। पहला कॉल d2.call_impl() कुछ यादृच्छिक बिट्स आउटपुट करेगा जो D2::data_ के रूप में अपेक्षित नहीं थे। दूसरी और तीसरी कॉल data_ के लिए सभी 100 आउटपुट करेंगे।

मुझे समझ में आता है कि यह क्यों संकलित और चलाएगा, अगर मैं गलत हूं तो मुझे सही करें।

जब हम कॉल d2.call() बनाने के लिए, कॉल Base<D1>::call को हल हो गई है, और कहा कि D1 को this डाली और D1::call_impl कॉल करेंगे। क्योंकि D1 वास्तव में Base<D1> रूप से निकाला गया है, इसलिए कास्ट संकलन समय पर ठीक है।

रन समय

, कलाकारों के बाद, this, यह वास्तव में एक D2 वस्तु व्यवहार किया जाता है, जबकि के रूप में अगर यह D1 है, और D1::call_impl इच्छा के कॉल स्मृति बिट्स कि D1::data_, और उत्पादन होने की अपेक्षा की जाती है संशोधित। इस मामले में, ये बिट्स D2::data_ हैं। मुझे लगता है कि दूसरा d2.call_impl() सी ++ कार्यान्वयन के आधार पर अपरिभाषित व्यवहार भी होगा।

बिंदु यह है कि, यह कोड, जबकि गहन रूप से गलत है, उपयोगकर्ता को त्रुटि का कोई संकेत नहीं देगा। मैं वास्तव में अपनी परियोजना में क्या कर रहा हूं कि मेरे पास एक सीआरटीपी बेस क्लास है जो एक प्रेषण इंजन की तरह कार्य करता है। लाइब्रेरी में एक और वर्ग सीआरटीपी बेस क्लास इंटरफ़ेस तक पहुंचता है, call कहता है, और callcall_dispatch पर प्रेषित होगा जो बेस क्लास डिफ़ॉल्ट कार्यान्वयन या व्युत्पन्न क्लास कार्यान्वयन हो सकता है। यदि उपयोगकर्ता व्युत्पन्न कक्षा को परिभाषित करते हैं, तो D कहें, ये सभी ठीक काम करेंगे, वास्तव में Base<D> से प्राप्त किए गए हैं। यह संकलन समय त्रुटि को बढ़ाएगा यदि यह Base<Unrelated> से लिया गया है जहां UnrelatedBase<Unrelated> से नहीं लिया गया है। लेकिन यह उपरोक्त की तरह उपयोगकर्ता लिखने कोड को नहीं रोकेगा।

उपयोगकर्ता आधार सीआरटीपी कक्षा से प्राप्त करके पुस्तकालय का उपयोग करते हैं और कुछ कार्यान्वयन विवरण प्रदान करते हैं। निश्चित रूप से अन्य डिज़ाइन विकल्प हैं जो उपरोक्त के रूप में गलत उपयोग की समस्या से बच सकते हैं (उदाहरण के लिए एक सार आधार वर्ग)। लेकिन चलो अब उन्हें एक तरफ रख दें और सिर्फ मुझे विश्वास करें कि किसी कारण से मुझे इस डिजाइन की ज़रूरत है।

तो मेरा सवाल यह है कि, क्या कोई तरीका है कि मैं उपयोगकर्ता को उपरोक्त के रूप में गलत व्युत्पन्न कक्षा लिखने से रोक सकता हूं। यही है, यदि उपयोगकर्ता व्युत्पन्न कार्यान्वयन वर्ग लिखते हैं, तो D कहें, लेकिन उन्होंने इसे Base<OtherD> से लिया, फिर संकलन समय त्रुटि उठाई जाएगी।

एक समाधान dynamic_cast का उपयोग किया जाता है। हालांकि, यह विशाल है और यहां तक ​​कि जब यह काम करता है यह एक रन-टाइम त्रुटि है।

+0

संक्षिप्त उत्तर: नहीं। 'dynamic_cast' महंगा हो सकता है, लेकिन यह आपके उपयोगकर्ताओं को ठीक करने की कोशिश करने से सस्ता है। – Rook

+1

कम से कम असफल गतिशील_कास्ट यूनिट परीक्षणों में तेजी से मिलेगा। आप संकलक, हर संभव टाइपो से बचाने 'लेखन मैं 1' + जब' अर्थ मैं की तरह नहीं हो सकता है - 1'। यह समान है। –

+2

संभावित डुप्लिकेट [सीआरटीपी का उपयोग करते समय त्रुटियों से कैसे बचें?] (Http://stackoverflow.com/questions/4417782/how-to-avoid-errors-while-using-crtp) –

उत्तर

32

1) (अगर कोई निर्माता ये हैं, एक को जोड़ने)

2) बेस निजी के सभी निर्माताओं बनाने बेस

template <class Derived> 
class Base 
{ 
private: 

    Base(){}; // prevent undesirable inheritance making ctor private 
    friend Derived; // allow inheritance for Derived 

public : 

    void call() 
    { 
     static_cast<Derived *>(this)->call_impl(); 
    } 
}; 

के मित्र के रूप में व्युत्पन्न टेम्पलेट पैरामीटर की घोषणा इस के बाद यह असंभव होगा गलत विरासत डी 2 के किसी भी उदाहरण बनाएँ।

+0

मुझे लगता है कि यह काम करेगा! ये वास्तव में स्मार्ट चीज हमेशा सचमुच सरल क्यों होती है! यह एक बार्टन-नाकमैन चाल की तरह दिखता है –

+0

@ यान झोउ: मैंने कोशिश की, ऐसा लगता है :) मुझे भी SO पर एक संबंधित प्रश्न मिला: http://stackoverflow.com/questions/5907731/how-to -इम्प्यूमेंट-ए-कंपाइल-टाइम-चेक-द-ए-डाउनकास्ट-ए-वैध-इन-ए-सीआरटीपी है और इसका उत्तर भी दिया है, लेकिन, शायद कुछ समस्याएं हैं क्योंकि प्रश्न अधिक सामान्य दिखाई देता है (गलत विरासत के बारे में नहीं केवल)। – user396672

+2

@ user396672: केवल एक ही गड़बड़ मैं देख सकता हूं कि यह सी ++ 11 से पहले खराब हो गया है। क्लास टेम्पलेट वाले स्पष्ट रूप से अस्वीकृत मानक एक टेम्पलेट प्रकार-पैरामीटर को मित्र के रूप में घोषित करता है। –

2

सामान्य बिंदु: टेम्पलेट गलत पैरा के साथ तत्काल होने से सुरक्षित नहीं हैं। यह अच्छी तरह से ज्ञात मुद्दा है। इसे ठीक करने की कोशिश में समय बिताने की सिफारिश नहीं की जाती है। टेम्पलेट्स का दुरुपयोग कैसे किया जा सकता है या संख्या अंतहीन है। आपके विशेष मामले में आप कुछ खोज सकते हैं। बाद में आप अपने कोड को संशोधित करेंगे और दुरुपयोग के नए तरीके दिखाए जाएंगे।

मुझे पता है कि सी ++ 11 में स्थिर जोर है जो मदद कर सकता है। मुझे पूरा विवरण नहीं पता।

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

पेज। हमारी कंपनी इस क्षेत्र में सेवाएं प्रदान करती है।

+1

मैं वास्तव में यहां कुछ भी नहीं ढूंढ सकता जो उसके प्रश्न का उत्तर दे, केवल आपकी कंपनी के लिए पदोन्नति ... – PlasmaHH

+0

चलो। मैं कह रहा हूं कि: 1. सी ++ 2003 गलत टेम्पलेट तत्कालता के लिए अच्छी सुरक्षा नहीं दे रहा है। 2. सी ++ 11 मदद कर सकता है। 3. एल्गोरिदमिक विश्लेषण मदद कर सकता है। हमारे उपकरणों की मदद से जरूरी नहीं है। मैं कहां कह रहा हूं कि अन्य उपकरण काम नहीं करेंगे? –

+0

वह पूछ रहा है/क्या/मदद करता है, बिल्कुल। यह कहकर कि वहां कुछ ऐसा है जो मदद कर सकता है जितना मुझे लगता है कि वह पहले से ही – PlasmaHH

4

यदि आपके पास सी ++ 11 उपलब्ध है, तो आप static_assert का उपयोग कर सकते हैं (यदि नहीं, मुझे यकीन है कि आप इन चीजों को बढ़ावा देने के साथ अनुकरण कर सकते हैं)। आप उदाहरण के लिए जोर दे सकते हैं is_convertible<Derived*,Base*> या is_base_of<Base,Derived>

यह सब बेस में होता है, और यह सब कभी व्युत्पन्न की जानकारी है। यह देखने का मौका कभी नहीं होगा कि कॉलिंग संदर्भ डी 2 या डी 1 से है, क्योंकि इससे कोई फर्क नहीं पड़ता है, क्योंकि Base<D1> एक बार एक विशिष्ट तरीके से तत्काल चालू होता है, इससे कोई फर्क नहीं पड़ता कि इसे डी 1 या डी 2 से प्राप्त किया गया है या नहीं (या उपयोगकर्ता द्वारा स्पष्ट रूप से इसे तुरंत चालू करना)।

जब से तुम नहीं करने के लिए (जाहिर है, क्योंकि यह कभी कभी महत्वपूर्ण क्रम लागत और स्मृति भूमि के ऊपर है) चाहते dynamic_cast उपयोग करते हैं, अक्सर "पाली डाली" कहा जाता है कुछ इस्तेमाल करने की कोशिश (बढ़ावा अपनी ही संस्करण भी है):

template<class R, class T> 
R poly_cast(T& t) 
{ 
#ifndef NDEBUG 
     (void)dynamic_cast<R>(t); 
#endif 
     return static_cast<R>(t); 
} 

इस तरह से आपके डीबग/परीक्षण में त्रुटि का पता चला है। हालांकि 100% गारंटी नहीं है, अभ्यास में यह अक्सर लोगों की सभी गलतियों को पकड़ता है।

+1

मेरे पास एक ही विचार था, लेकिन ऐसा लगता है कि यह उतना आसान नहीं है। यदि आप इसे 'stat' के शरीर में 'static_assert (is_base_of <बेस , व्युत्पन्न> :: मान," ... ") के रूप में उपयोग करते हैं, तो यह काम नहीं करेगा, क्योंकि अगर टेम्पलेट को' डी 1 'के साथ तत्काल प्राप्त किया जाता है 'static_assert बन जाएगा (is_base_of < Base< D1 >, डी 1> :: मान," ... ") 'जो जोर नहीं देता है (सभी' डी 1' 'बेस < D1 >' से लिया गया है)। इस कैच की एकमात्र त्रुटियां वे हैं जो 'बेस < D3 >' से प्राप्त होती हैं, जहां 'D3' स्वयं को सही 'बेस' से प्राप्त नहीं करता है। हालांकि इन त्रुटियों को 'static_cast' द्वारा पहले से ही कब्जा कर लिया गया है। – LiKao

+0

@ लीकाओ: आह, अब मैं देखता हूं कि आपका क्या मतलब है, मुझे इसे उत्तर में जोड़ने दें। – PlasmaHH

+0

पॉली कास्ट संकेत के लिए धन्यवाद। मैं बस एक बहुत ही समान विचार के साथ आया हूँ। मैं डीबग मोड में गतिशील_कास्ट पर जोर देता हूं और फिर static_cast करता हूं। टेम्पलेट समाधान स्पष्ट रूप से भी बहुत ही सुरुचिपूर्ण है। पॉली कास्ट के लिए –

1

आप के साथ सी ++ 11 गिनती नहीं कर सकते हैं, तो आप इस चाल की कोशिश कर सकते:

स्थिर व्युत्पन्न:

  1. Base कि अपनी specialied प्रकार के सूचक के रिटर्न में एक स्थिर समारोह जोड़ें * व्युत्पन्न() {वापसी न्यूल; }

  2. आधार के लिए एक स्थिर check समारोह टेम्पलेट है कि एक सूचक लेता जोड़ें:

    टेम्पलेट < typename टी> स्थिर bool जांच (टी * derived_this) { वापसी (derived_this == बेस < व्युत्पन्न>: :निकाली गई()); }

  3. अपने Dn कंस्ट्रक्टर्स में, फोन check(this):

    चेक (यह)

अब

अगर आप को संकलित करने का प्रयास करें:

$ g++ -Wall check_inherit.cpp -o check_inherit 
check_inherit.cpp: In instantiation of ‘static bool Base<Derived>::check(T*) [with T = D2; Derived = D1]’: 
check_inherit.cpp:46:16: required from here 
check_inherit.cpp:19:62: error: comparison between distinct pointer types ‘D2*’ and ‘D1*’ lacks a cast                                
check_inherit.cpp: In static member function ‘static bool Base<Derived>::check(T*) [with T = D2; Derived = D1]’:                             
check_inherit.cpp:20:5: warning: control reaches end of non-void function [-Wreturn-type]                                   
1

सामान्य मुझे नहीं लगता कि में इसे पाने का एक तरीका है, जिसे पूरी तरह से बदसूरत नहीं माना जाना चाहिए और बुराई सुविधाओं के उपयोग में वापस आना चाहिए। यहां सारांश है कि क्या काम करेगा और क्या नहीं होगा।

  • static_assert का उपयोग (या तो सी ++ 11 या बढ़ावा से से) काम नहीं करता है, क्योंकि Base की परिभाषा में एक चेक केवल प्रकार Base<Derived> और Derived उपयोग कर सकते हैं। तो निम्नलिखित अच्छे लग जाएगा, लेकिन असफल:

    template <typename Derived> 
    class Base 
    { 
        public : 
    
        void call() 
        { 
         static_assert(sizeof(Derived) != 0 && std::is_base_of< Base<Derived>, Derived >::value, "Missuse of CRTP"); 
         static_cast<Derived *>(this)->call_impl(); 
        } 
    }; 
    

मामले में आप घोषित करने के लिए D2class D2 : Base<D1> के रूप में स्थिर ज़ोर इस पकड़ नहीं होगा की कोशिश, D1 वास्तव Base<D1> से ली गई है और स्थिर ज़ोर पूरी तरह से है, क्योंकि वैध। यदि आप Base<D3> से प्राप्त करते हैं, जहां D3Base<D3> से static_assert दोनों के साथ-साथ static_cast दोनों संकलन त्रुटियों को ट्रिगर करेगा, तो यह बिल्कुल बेकार है।

के बाद प्रकार D2 आप Base के कोड में जाँच करने के लिए की आवश्यकता होगी टेम्पलेट को static_assert उपयोग करने के लिए D2 की घोषणाओं के बाद इसे स्थानांतरित करने के जो एक ही व्यक्ति है जो करने के लिए लागू किया D2 की आवश्यकता होगी होगा एक ही रास्ता पास नहीं हो जांचें, जो फिर बेकार है।

एक तरीका यह चारों ओर पाने के लिए एक मैक्रो जोड़कर होगा, लेकिन यह कुछ भी नहीं है लेकिन शुद्ध कुरूपता पैदा होगा:

#define MAKE_DISPATCHABLE_BEGIN(DeRiVeD) \ 
    class DeRiVeD : Base<DeRiVed> { 
#define MAKE_DISPATCHABLE_END(DeRiVeD) 
    }; \ 
    static_assert(is_base_of< Base<Derived>, Derived >::value, "Error"); 

यह केवल लाभ कुरूपता, और static_assert फिर superflous है, क्योंकि टेम्पलेट सुनिश्चित करती है कि प्रकार हमेशा मेल खाते हैं। तो यहां कोई लाभ नहीं है।

  • सर्वश्रेष्ठ विकल्प: इस सब के बारे में भूल जाओ और dynamic_cast का उपयोग करें जो स्पष्ट रूप से इस परिदृश्य के लिए था। यदि आपको इसकी अधिक आवश्यकता है तो शायद यह आपके asserted_cast को लागू करने के लिए समझ में आएगा (इस पर डॉ जॉब्स पर एक लेख है), जो dynamic_cast विफल होने पर स्वचालित रूप से विफल असफलता को ट्रिगर करता है।
1

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

template < typename Derived > 
void safe_call(Derived& t) 
{ 
    static_cast< Base<Derived>& >(t).call(); 
} 

या कोई पदानुक्रम के कई स्तर होते हैं, निम्नलिखित पर विचार:

template < typename Derived, 
      typename BaseArg > 
void safe_call_helper(Derived& d, 
         Base<BaseArg>& b) 
{ 
    // Verify that Derived does inherit from BaseArg. 
    static_cast< BaseArg& >(d).call(); 
} 

template < typename T > 
void safe_call(T& t) 
{ 
    safe_call_helper(t, t); 
} 

thse दोनों मामलों में, safe_call(d1) जबकि safe_call(d2) संकलित करने के लिए असफल हो जायेगी संकलित कर देगा। संकलक त्रुटि उपयोगकर्ता के लिए जितनी चाहें उतनी स्पष्ट नहीं हो सकती है, इसलिए स्थिर आवेषणों पर विचार करना उचित हो सकता है।

संबंधित मुद्दे