2009-03-23 28 views
12

की मैं इस तरह एक struct है लगता है:नाम से संरचना चर का उपयोग करने के लिए टेम्पलेट का उपयोग किया जा सकता है?

struct my_struct 
{ 
    int a; 
    int b; 
} 

मैं एक समारोह है जो या तो "एक" या "b" के लिए एक नया मान निर्धारित करना चाहिए है। इस फ़ंक्शन को यह निर्दिष्ट करने की भी आवश्यकता है कि कौन सा चर सेट करना है। एक विशिष्ट उदाहरण इस तरह होगा:

void f(int which, my_struct* s, int new_value) 
{ 
    if(which == 0) 
    s->a = new_value; 
    else 
    s->b = new_value; 
} 

कारणों मैं यहाँ नहीं लिखेंगे के लिए मैं च के लिए ख एक/सूचक पारित नहीं कर सकते हैं। इसलिए मैं my_struct :: a या my_struct :: b के पते के साथ f को कॉल नहीं कर सकता। एक और चीज जो मैं नहीं कर सकता वह है कि वेक्टर (इंट वर्र्स [2]) को my_struct में घोषित करें और एफ को इंडेक्स के रूप में एक पूर्णांक पास करें। मूल रूप से f में मुझे चर से चर का उपयोग करने की आवश्यकता है।

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

#define FUNC(which) 
void f(my_struct* s, int new_value) \ 
{ \ 
     s->which = new_value; \ 
} 

और फिर मैं समारोह कह सकते हैं (क) या समारोह (ख)।

यह काम करेगा लेकिन मुझे मैक्रोज़ का उपयोग करना पसंद नहीं है। तो मेरा सवाल है: क्या मैक्रोज़ के बजाय टेम्पलेट का उपयोग करके एक ही लक्ष्य प्राप्त करने का कोई तरीका है?

EDIT: मैं यह बताने की कोशिश करूंगा कि मैं पॉइंटर्स का उपयोग क्यों नहीं कर सकता और मुझे नाम से परिवर्तनीय तक पहुंच की आवश्यकता है। मूल रूप से संरचना में एक प्रणाली की स्थिति होती है। अनुरोध किए जाने पर इस सिस्टम को अपने राज्य को "पूर्ववत" करने की आवश्यकता है। (Mystruct अन्य प्रकार के चर के रूप में अच्छी तरह से शामिल है)

class undo_token 
{ 
public: 
    void undo(my_struct* s) = 0; 
}; 

तो मैं बहुरूपता की वजह से पूर्ववत विधि की ओर इशारा पारित नहीं कर सकते हैं: पूर्ववत करें इस तरह undo_token नामक एक इंटरफ़ेस का उपयोग कर नियंत्रित किया जाता है।

जब मैं एक नया वेरिएबल संरचना करने के लिए मैं आम तौर पर भी, एक नया वर्ग को जोड़ने के इस तरह जोड़ें:

class undo_a : public undo_token 
{ 
    int new_value; 
public: 
    undo_a(int new_value) { this->new_value = new_value; } 
    void undo(my_struct *s) { s->a = new_value} 
}; 

समस्या जब मैं टोकन बनाने मैं रों सूचक नहीं जानता है, इसलिए मैं नहीं कर सकता एक सूचक को एस :: को कन्स्ट्रक्टर में सहेजें (जो समस्या हल कर लेगा)। "बी" के लिए कक्षा समान है, बस मुझे एस->

शायद यह एक डिज़ाइन समस्या है: मुझे प्रति चर प्रकार पूर्ववत टोकन की आवश्यकता है, एक नहीं प्रति चर ...

+0

FUNC (ए) - एफ (एस, new_value) परिभाषित करेगा। लेकिन फिर आप इसे कैसे बुलाएंगे? –

+0

हाँ क्षमा करें कि एक गलती थी। लेकिन मुझे उम्मीद है कि आपको वैसे भी बिंदु मिल जाएगा :) – Emiliano

+0

मेरे संपादन के बाद भी, मेरे उत्तर और माइकोला गोलबुएव के उपयोग से, आप अभी भी अपने पूर्व वर्ग को क्लास डेटा सदस्य के सूचक के साथ पैरामीटर कर सकते हैं और इसे सदस्य आमंत्रण पर कक्षा/संरचना में बाध्य कर सकते हैं पहर। – camh

उत्तर

16
#include <iostream> 
#include <ostream> 
#include <string> 

struct my_struct 
{ 
    int a; 
    std::string b; 
}; 

template <typename TObject, typename TMember, typename TValue> 
void set(TObject* object, TMember member, TValue value) 
{ 
    (*object).*member = value; 
} 

class undo_token {}; 

template <class TValue> 
class undo_member : public undo_token 
{ 
    TValue new_value_; 
    typedef TValue my_struct::* TMember; 
    TMember member_; 

public: 
    undo_member(TMember member, TValue new_value): 
     new_value_(new_value), 
     member_(member) 
    {} 

    void undo(my_struct *s) 
    { 
     set(s, member_, new_value_); 
    } 
};  

int main() 
{ 
    my_struct s; 

    set(&s, &my_struct::a, 2); 
    set(&s, &my_struct::b, "hello"); 

    std::cout << "s.a = " << s.a << std::endl; 
    std::cout << "s.b = " << s.b << std::endl; 

    undo_member<int> um1(&my_struct::a, 4); 
    um1.undo(&s); 

    std::cout << "s.a = " << s.a << std::endl; 

    undo_member<std::string> um2(&my_struct::b, "goodbye"); 
    um2.undo(&s); 

    std::cout << "s.b = " << s.b << std::endl; 

    return 0; 
} 
+0

क्या आपने थोड़ा सा पढ़ा जहां उसने कहा कि वह ए या बी का पता नहीं ले सकता? –

+0

क्या इसका मतलब यह नहीं है कि वह एक समारोह में चीजें जैसे एसए पास नहीं कर सकता है। –

+0

बिल्कुल सही! यह मेरी समस्या हल हो गई। चालाक और सरल।धन्यवाद – Emiliano

2

आप इसे हल करने के लिए टेम्पलेट का उपयोग नहीं कर सकते हैं, लेकिन पहले स्थान पर संरचना का उपयोग क्यों करें? यह एक std :: मैप के लिए आदर्श उपयोग जैसा प्रतीत होता है जो नामों के नामों को मानचित्र करेगा।

+1

क्योंकि "एफ" संरचना पर माध्यमिक सामान करने के लिए केवल एक समारोह की आवश्यकता है। मैं नहीं चाहता कि संरचना आकार "एफ" की जरूरतों के अनुरूप हो। – Emiliano

4

मुझे यकीन नहीं है कि आप एक सूचक का उपयोग क्यों नहीं कर सकते हैं, इसलिए मुझे नहीं पता कि यह उचित है या नहीं, लेकिन C++: Pointer to class data member पर एक नज़र डालें, जो कि एक स्ट्रक्चर/डेटा के डेटा सदस्य को पॉइंटर पास करने का तरीका बताता है। कक्षा जो सीधे सदस्य को इंगित नहीं करती है, लेकिन बाद में इसे एक स्ट्रक्चर/क्लास पॉइंटर से बाध्य किया जाता है। (पोस्टर के संपादन के बाद जोर दिया गया है कि एक सूचक का उपयोग क्यों नहीं किया जा सकता है)

इस तरह आप सदस्य को सूचक नहीं देते हैं - इसके बजाय यह किसी ऑब्जेक्ट के भीतर ऑफसेट की तरह है।

3

यह आप के लिए "reflection" कहा जाता है जो खोज रहे हैं की तरह लगता है, और हाँ यह अक्सर टेम्पलेट्स और मैक्रोज़ के कुछ संयोजन के साथ लागू किया है। चेतावनी दीजिये कि प्रतिबिंब समाधान अक्सर काम करने के लिए गन्दा और परेशान होते हैं, ताकि आप यह पता लगाने के लिए कोड में डुबकी लेने से पहले कुछ शोध करना चाहें कि यह वास्तव में है जो आप चाहते हैं।

"सी ++ प्रतिबिंब टेम्पलेट्स" के लिए Google पर दूसरी हिट "Reflection support by means of template metaprogramming" पर एक पेपर थी। आपको इस तरह से प्रारंभ करवाया जाना चाहिए। यहां तक ​​कि यदि आप जो कुछ भी ढूंढ रहे हैं वह पर्याप्त नहीं है, तो यह आपको अपनी समस्या का समाधान करने का एक तरीका दिखा सकता है।

2

आपने जो वर्णन किया है, उससे मैं अनुमान लगा रहा हूं कि आपके पास संरचना को फिर से परिभाषित करने का कोई तरीका नहीं है।

यदि आपने किया है, तो मैं सुझाव दूंगा कि आप बूस्ट.फ्यूजन का उपयोग टेम्पलेट-नाम वाले फ़ील्ड के साथ अपनी संरचना का वर्णन करने के लिए करें। उस पर अधिक जानकारी के लिए associative tuples देखें। दोनों प्रकार की संरचनाएं वास्तव में संगत (स्मृति में एक ही संगठन) हो सकती हैं, लेकिन मुझे पूरा यकीन है कि मानक से ऐसी गारंटी प्राप्त करने का कोई तरीका नहीं है।

यदि आप नहीं करते हैं, तो आप संरचना के लिए एक पूरक बना सकते हैं जो आपको उन क्षेत्रों तक पहुंच प्रदान करेगा जैसे सहयोगी टुपल्स करते हैं। लेकिन यह थोड़ा मौखिक हो सकता है।

संपादित

अब यह बहुत स्पष्ट है कि आप संरचनाओं मनचाहे तरीके से परिभाषित कर सकते हैं। तो मैं निश्चित रूप से सुझाव देता हूं कि आप boost.fusion का उपयोग करें।

34

सटीक प्रश्न का उत्तर देने के लिए, लेकिन यह बहुत जटिल है, और यह पूरी तरह से एक संकलन-समय की बात होगी। (यदि आपको रनटाइम लुकअप की आवश्यकता है, तो एक पॉइंटर-टू-सदस्य का उपयोग करें - और आपके अपडेट किए गए प्रश्न के आधार पर, आपने गलत तरीके से काम किया है कि वे कैसे काम करते हैं।)

सबसे पहले, आपको उस चीज़ की आवश्यकता है जिसका उपयोग आप " सदस्य "संकलन समय पर। संकलन-समय मेटाप्रोग्रामिंग में, पूर्णांक के अलावा सबकुछ को प्रकारों द्वारा प्रदर्शित किया जाना चाहिए। तो आप एक सदस्य का प्रतिनिधित्व करने के लिए एक प्रकार का उपयोग करेंगे।

उदाहरण के लिए, प्रकार पूर्णांक के एक सदस्य है कि एक व्यक्ति की आयु संग्रहीत करता है, और एक अन्य उनके अंतिम नाम के भंडारण के लिए:

struct age { typedef int value_type; }; 
struct last_name { typedef std::string value_type; }; 

तो फिर तुम एक map कि संकलन समय पर देखने करता है की तरह कुछ की जरूरत है। चलो इसे ctmap कहा जाता है। आइए इसे 8 सदस्यों तक समर्थन दें। सबसे पहले हम एक प्लेसहोल्डर जरूरत है एक क्षेत्र की अनुपस्थिति का प्रतिनिधित्व करने के लिए:

struct none { struct value_type {}; }; 

फिर हम ctmap के आकार को आगे-घोषणा कर सकते हैं:

template < 
    class T0 = none, class T1 = none, 
    class T2 = none, class T3 = none, 
    class T4 = none, class T5 = none, 
    class T6 = none, class T7 = none 
    > 
struct ctmap; 

हम तो मामले के लिए इस विशेषज्ञ जहां कोई फ़ील्ड देखते हैं :

template <> 
struct ctmap< 
    none, none, none, none, 
    none, none, none, none 
    > 
{ 
    void operator[](const int &) {}; 
}; 

इसका कारण स्पष्ट रूप से (संभवतः) एक पल में आ जाएगा। अंत में, अन्य सभी मामलों की परिभाषा:

template < 
    class T0, class T1, class T2, class T3, 
    class T4, class T5, class T6, class T7 
    > 
    struct ctmap : public ctmap<T1, T2, T3, T4, T5, T6, T7, none> 
    { 
     typedef ctmap<T1, T2, T3, T4, T5, T6, T7, none> base_type; 

     using base_type::operator[]; 
     typename T0::value_type storage; 

     typename T0::value_type &operator[](const T0 &c) 
     { return storage; } 
}; 

यहां क्या चल रहा है?यदि आप रख:

ctmap<last_name, age> person; 

सी ++, रिकर्सिवली टेम्पलेट्स का विस्तार करके person के लिए एक प्रकार का निर्माण करेगा क्योंकि खुद से ctmapइनहेरिट करती है, और हम पहले क्षेत्र के लिए भंडारण प्रदान करते हैं और फिर इसे त्यागने जब हम वारिस। यह सब एक अचानक रोक के लिए आता है जब वहाँ कोई और अधिक क्षेत्र हैं, क्योंकि में सब none किक के लिए विशेषज्ञता

तो हम कह सकते हैं:।

person[last_name()] = "Smith"; 
person[age()] = 104; 

यह एक map में देख तरह है, लेकिन पर कुंजी के रूप में फ़ील्ड-नामकरण वर्ग का उपयोग करके समय संकलित करें।

इसका मतलब यह है हम भी ऐसा कर सकते हैं:

template <class TMember> 
void print_member(ctmap<last_name, age> &person) 
{ 
    std::cout << person[TMember()] << std::endl; 
} 

एक समारोह है कि एक सदस्य के मूल्य, जहां सदस्य मुद्रित करने के लिए एक प्रकार पैरामीटर है प्रिंट है कि। इसलिए हम इसे इस तरह से कॉल कर सकते हैं:

print_member<age>(person); 

तो हाँ, आप एक बात यह है कि एक struct, एक छोटे से एक संकलन समय map की तरह की तरह एक छोटे से है लिख सकते हैं।

+5

दुनिया में बिल्कुल सही चीज़ नहीं है, लेकिन फिर भी चालाक है। मुझे कुछ नया सिखाने के लिए +1। –

+2

एक बहुत ही पूरा उत्तर है, लेकिन यह मेरी जरूरत के लिए एक तरह का ओवरकिल है। जैसा कि दान ने कहा, आपको कुछ नया सिखाए जाने के लिए +1 मिला है, वैसे भी। – Emiliano

+0

धन्यवाद। मुझे आशा है कि कोई इस पर ठोकर खाएगा और इसे उपयोगी लगेगा - प्रश्न का शीर्षक इस तरह काम करने की संभावना प्रतीत होता है! –

1

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

आप अपने EDIT अनुभाग में सही हैं। यह डिज़ाइन का विषय है।

+0

क्योंकि कोड में कुछ बिंदु हैं जहां सिस्टम स्थिति को "चेकपॉइंट्स" द्वारा परिभाषित किया गया है। यह वर्तमान स्थिति को हटाने और एक नया निर्माण करने के लिए प्रेरित है। यह सच है कि 90% मामलों में पॉइंटर समान है, लेकिन यह हमेशा मामला नहीं है। – Emiliano

+0

यदि आप कोई राज्य ऑब्जेक्ट हटाते हैं और एक नया बनाते हैं, तो आपको पिछली स्थिति को मिटाने का कोई तरीका क्यों नहीं मिलता है, लेकिन इसे अक्षम करने के लिए, ताकि यदि आप हटाए जाने को पूर्ववत करते हैं, तो आप एक बार फिर से काम कर रहे हैं ऑब्जेक्ट? मैं स्पष्ट नहीं हो सकता, क्षमा करें, लेकिन मुझे यकीन नहीं है कि मैं अच्छी तरह से समझ गया! –

+0

वैसे मुझे लगता है कि अगर मैं जोर दे सकता हूं कि सिस्टम स्टेट पॉइंटर हमेशा मेरे प्रश्न में कोई बिंदु नहीं है तो वैध है। फिर भी, मुझे यकीन नहीं है कि मौजूदा कोड को संशोधित करना ताकि वह "समर्थन" करने के लिए उपयुक्त हो, एक अच्छा विचार है। नील बटरवर्थ उत्तर में भी मेरी टिप्पणी देखें। – Emiliano

6

Mykola Golubyev's answer अच्छा है, लेकिन यह सच है कि सदस्यों को संकेत दिए गए गैर प्रकार टेम्पलेट पैरामीटर के रूप में इस्तेमाल किया जा सकता का उपयोग करके थोड़ा सुधार किया जा सकता:

#include <iostream> 
#include <ostream> 
#include <string> 

struct my_struct 
{ 
    int a; 
    std::string b; 
}; 

template <typename TObject, typename TMember, typename TValue> 
void set(TObject* object, TMember member, TValue value) 
{ 
    (*object).*member = value; 
} 

class undo_token {}; 

template <class TValue, TValue my_struct::* Member> 
class undo_member : public undo_token 
{ 
     // No longer need to store the pointer-to-member 
     TValue new_value_; 

public: 
     undo_member(TValue new_value): 
       new_value_(new_value) 
     {} 

     void undo(my_struct *s) 
     { 
       set(s, Member, new_value_); 
     } 
};  

int main() 
{ 
    my_struct s; 

    set(&s, &my_struct::a, 2); 
    set(&s, &my_struct::b, "hello"); 

    std::cout << "s.a = " << s.a << std::endl; 
    std::cout << "s.b = " << s.b << std::endl; 

    undo_member<int, &my_struct::a> um1(4); 
    um1.undo(&s); 

    std::cout << "s.a = " << s.a << std::endl; 

    undo_member<std::string, &my_struct::b> um2("goodbye"); 
    um2.undo(&s); 

    std::cout << "s.b = " << s.b << std::endl; 

    return 0; 
} 

इस से सदस्य के लिए एक सूचक की लागत बंद shaves undo_member का प्रत्येक उदाहरण।

+0

+1। इसे कभी नहीं देखा। लेकिन यह प्रत्येक संरचना सदस्य के लिए बहुत से वर्ग बनाएगा। –

+0

@ मिकोला: यह सच है, लेकिन क्या यह एक समस्या है? विधियों को सभी संकलक द्वारा रेखांकित किया जाएगा, इसलिए मुझे नहीं लगता कि कोई कोड ब्लोट होगा। –

+0

वे मूल प्रश्न में, पूर्ववत विधि वर्चुअल (अच्छी तरह से, होना चाहिए - बेस क्लास में = 0 को दूर देता है, लेकिन वर्चुअल कीवर्ड ऐसा लगता है कि यह गलती से छोड़ा गया था) – camh

7

Daniel Earwicker's answer के अतिरिक्त, हम इसे प्राप्त करने के लिए नए सी ++ मानक में विविध टेम्पलेट का उपयोग कर सकते हैं।

template <typename T> 
struct Field { 
    typename T::value_type storage; 

    typename T::value_type &operator[](const T &c) { 
    return storage; 
    } 
}; 

template<typename... Fields> 
struct ctmap : public Field<Fields>... { 
}; 

यह कोड क्लीनर है और इसमें सदस्यों की निश्चित सीमा नहीं है। आप इसे उसी तरह उपयोग कर सकते हैं

struct age { typedef int value_type; }; 
struct last_name { typedef std::string value_type; }; 

ctmap<last_name, age> person; 

person[last_name()] = "Smith"; 
person[age()] = 104; 
+2

आप [एक कदम आगे] जा सकते हैं (http://coliru.stacked-crooked.com/a/c5ec6cdf781525a1), जहां हम उपयोग के क्षेत्र के करीब भी पहुंचते हैं। – Yakk

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

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