एक फ़ंक्शन कॉल C ++ का उपयोग करके कई निरंतर वर्ग के सदस्यों को प्रारंभ करें


50

यदि मेरे दो अलग-अलग सदस्य हैं, तो दोनों को एक ही फ़ंक्शन कॉल के आधार पर आरंभीकृत करने की आवश्यकता है, क्या फ़ंक्शन को दो बार कॉल किए बिना ऐसा करने का कोई तरीका है?

उदाहरण के लिए, एक अंश वर्ग जहाँ अंश और हर एक स्थिर होते हैं।

int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator(a/gcd(a,b)), denominator(b/gcd(a,b))
    {

    }
private:
    const int numerator, denominator;
};

इससे समय बर्बाद होता है, क्योंकि जीसीडी फ़ंक्शन को दो बार कहा जाता है। आप एक नए वर्ग के सदस्य को भी परिभाषित कर सकते हैं gcd_a_b, और सबसे पहले आरंभिक सूची में gcd के उत्पादन को निर्दिष्ट कर सकते हैं, लेकिन तब यह व्यर्थ स्मृति को जन्म देगा।

सामान्य तौर पर, क्या यह बर्बाद किए गए फ़ंक्शन कॉल या मेमोरी के बिना ऐसा करने का एक तरीका है? क्या आप शायद इनिशियलाइज़र सूची में अस्थायी चर बना सकते हैं? धन्यवाद।


5
क्या आपके पास सबूत है कि "जीसीडी फ़ंक्शन को दो बार कहा जाता है"? यह दो बार उल्लेख किया गया है, लेकिन यह एक संकलक उत्सर्जन कोड के समान नहीं है जो इसे दो बार कॉल करता है। एक संकलक यह कह सकता है कि यह एक शुद्ध कार्य है और दूसरे उल्लेख पर इसके मूल्य का पुन: उपयोग करता है।
एरिक टॉवर्स

6
@ EricTowers: हाँ, कंपाइलर कभी-कभी कुछ मामलों के लिए अभ्यास में समस्या के आसपास काम कर सकते हैं। लेकिन केवल अगर वे परिभाषा (या किसी वस्तु में कुछ एनोटेशन) देख सकते हैं, अन्यथा यह साबित करने का कोई तरीका नहीं है। आप चाहिए लिंक समय अनुकूलन के साथ सक्षम संकलन, लेकिन सभी के करता है। और समारोह एक पुस्तकालय में हो सकता है। या फिर एक समारोह है कि के मामले पर विचार करता है दुष्प्रभाव, और यह ठीक एक बार बुला शुद्धता की बात है?
पीटर कॉर्डेस

@EricTowers दिलचस्प बिंदु। मैंने वास्तव में जीसीडी फ़ंक्शन के अंदर एक प्रिंट स्टेटमेंट डालकर इसे जांचने का प्रयास किया था, लेकिन अब मुझे एहसास हुआ कि यह एक शुद्ध कार्य होने से रोकेगा।
Qq0

@ Qq0: आप संकलित जनरेटर को देखकर जाँच कर सकते हैं, उदाहरण के लिए gcc या क्लैंग के साथ Godbolt संकलक एक्सप्लोरर का उपयोग करना-O3 । लेकिन शायद किसी भी सरल परीक्षण के कार्यान्वयन के लिए यह वास्तव में फ़ंक्शन कॉल को इनलाइन करेगा। यदि आप __attribute__((const))एक दृश्यमान परिभाषा प्रदान किए बिना प्रोटोटाइप पर उपयोग करते हैं या शुद्ध करते हैं, तो उसे दो कॉलों के बीच समान arg के साथ GCC या क्लैंग को सामान्य-सबप्रेसेशन एलिमिनेशन (CSE) करने देना चाहिए। ध्यान दें कि ड्रू का उत्तर गैर-शुद्ध कार्यों के लिए भी काम करता है, इसलिए यह बहुत बेहतर है और आपको इसका उपयोग किसी भी समय करना चाहिए जब तक कि फ़नल इनलाइन न हो।
पीटर कॉर्डेस

आम तौर पर, गैर-स्थैतिक कास्ट सदस्य चर सबसे अच्छा बचा जाता है। कुछ क्षेत्रों में से एक है जहाँ सब कुछ अक्सर लागू नहीं होता है। उदाहरण के लिए आप क्लास ऑब्जेक्ट नहीं दे सकते। आप वेक्टर में emplace_back कर सकते हैं, लेकिन केवल इतने लंबे समय तक जब तक कि क्षमता सीमा एक आकार बदलने में किक न करे।
दोपहर

जवाबों:


67

सामान्य तौर पर, क्या यह बर्बाद किए गए फ़ंक्शन कॉल या मेमोरी के बिना ऐसा करने का एक तरीका है?

हाँ। यह एक प्रतिनिधि निर्माणकर्ता के साथ किया जा सकता है , जिसे C ++ 11 में पेश किया गया है।

एक प्रतिनिधि निर्माणकर्ता किसी भी सदस्य चर को आरंभीकृत करने से पहले निर्माण के लिए आवश्यक अस्थायी मूल्यों को प्राप्त करने के लिए एक बहुत ही कुशल तरीका है।

int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
    // Call gcd ONCE, and forward the result to another constructor.
    Fraction(int a, int b) : Fraction(a,b,gcd(a,b))
    {
    }
private:
    // This constructor is private, as it is an
    // implementation detail and not part of the public interface.
    Fraction(int a, int b, int g_c_d) : numerator(a/g_c_d), denominator(b/g_c_d)
    {
    }
    const int numerator, denominator;
};

ब्याज से बाहर, एक और निर्माता को बुलाने से ओवरहेड महत्वपूर्ण होगा?
18

1
@ Qq0 आप यहाँ देख सकते हैं कि मामूली अनुकूलन के साथ कोई ओवरहेड सक्षम नहीं है।
ड्रू डॉर्मन

2
@ Qq0: C ++ को आधुनिक अनुकूलन कंपाइलर के आसपास बनाया गया है। वे इस प्रतिनिधिमंडल को तुच्छ रूप से इनलाइन कर सकते हैं, खासकर यदि आप इसे क्लास डेफिनेशन (इन .h) में दिखाई देते हैं , भले ही असली कंस्ट्रक्टर डेफिनिशन इनलाइनिंग के लिए दिखाई न दे रहा हो। यानी gcd()कॉल प्रत्येक कंस्ट्रक्टर कॉल इनलाइन में इनलाइन होगा, और केवल call3-ऑपरेंड प्राइवेट कंस्ट्रक्टर को छोड़ देगा।
पीटर कॉर्डेस

10

सदस्य संस्करण को उस क्रम से आरम्भ किया जाता है जिसे वे कक्षा घोषणा में घोषित करते हैं, इसलिए आप निम्न (गणितीय रूप से) कर सकते हैं

#include <iostream>
int gcd(int a, int b){return 2;}; // Greatest Common Divisor of (4, 6) just to test
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator{a/gcd(a,b)}, denominator(b/(a/numerator))
    {

    }
//private:
    const int numerator, denominator;//make sure that they are in this order
};
//Test
int main(){
    Fraction f{4,6};
    std::cout << f.numerator << " / " << f.denominator;
}

दूसरे कंस्ट्रक्टरों को बुलाने या उन्हें बनाने की भी जरूरत नहीं।


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

@PeterCordes लेकिन दूसरे समाधान में एक अतिरिक्त फ़ंक्शन कॉल है और अधिक अनुदेश मेमोरी आवंटित करता है।
asmmo

1
क्या आप ड्रू के प्रतिनिधि निर्माता के बारे में बात कर रहे हैं? यह स्पष्ट रूप Fraction(a,b,gcd(a,b))से कॉल करने वाले प्रतिनिधिमंडल को इनलाइन कर सकता है , जिससे कुल लागत कम हो जाएगी। संकलक के लिए इनलाइनिंग करना आसान है, ताकि अतिरिक्त विभाजन को पूर्ववत किया जा सके। मैंने Godbolt.org पर इसकी कोशिश नहीं की, लेकिन अगर आप उत्सुक हैं तो आप कर सकते हैं। -O3एक सामान्य बिल्ड का उपयोग करके gcc या क्लैंग का उपयोग करें। (सी ++ को एक आधुनिक अनुकूलन कंपाइलर की धारणा के आसपास बनाया गया है, इसलिए इस तरह की विशेषताएं constexpr)
पीटर कॉर्ड्स

-3

@ ड्रूमन ने मेरे दिमाग में जो बात थी, उसके समान एक समाधान दिया। चूंकि ओपी ने कभी भी ctor को संशोधित करने में सक्षम नहीं होने का उल्लेख किया है, इसलिए इसे इसके साथ बुलाया जा सकता है Fraction f {a, b, gcd(a, b)}:

Fraction(int a, int b, int tmp): numerator {a/tmp}, denominator {b/tmp}
{
}

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


3
आपका संपादन इस प्रश्न का उत्तर नहीं देता है। अब आपको कॉल करने वाले को 3rd arg पास करने की आवश्यकता है? कंस्ट्रक्टर बॉडी के अंदर असाइनमेंट का उपयोग करने वाला आपका मूल संस्करण काम नहीं करता है const, लेकिन कम से कम अन्य प्रकारों के लिए काम करता है। और आप किस अतिरिक्त विभाजन से "बच" रहे हैं? आपका मतलब बनाम अस्मो का जवाब है?
पीटर कॉर्डेस

1
ठीक है, अब मेरा पदच्युत हटा दिया है कि आपने अपनी बात समझाई है। लेकिन यह बहुत स्पष्ट रूप से भयानक लगता है, और आपको हर कॉलगर्ल में कुछ निर्माण कार्य को मैन्युअल रूप से इनलाइन करने की आवश्यकता होती है। यह DRY के विपरीत है (अपने आप को दोहराएं नहीं) और वर्ग की जिम्मेदारी / इंटर्नल का एनकैप्सुलेशन। अधिकांश लोग इसे स्वीकार्य समाधान नहीं मानेंगे। यह देखते हुए कि यह सफाई से करने के लिए एक C ++ 11 तरीका है, किसी को भी कभी भी ऐसा नहीं करना चाहिए जब तक कि वे पुराने C ++ संस्करण के साथ फंस न जाएं, और वर्ग के पास इस निर्माता के पास बहुत कम कॉल हैं।
पीटर कॉर्डेस

2
@aconcernedcitizen: मेरा प्रदर्शन कारणों से नहीं है, मेरा मतलब कोड-गुणवत्ता कारणों से है। अपने तरीके से, यदि आपने कभी यह बदला कि इस वर्ग ने आंतरिक रूप से कैसे काम किया है तो आपको कंस्ट्रक्टर को सभी कॉल को ढूंढना होगा और उस 3 आरजी को बदलना होगा। यह अतिरिक्त ,gcd(foo, bar)अतिरिक्त कोड है जो स्रोत में हर कॉललाइट से बाहर फैक्टर किया जा सकता है । यह एक स्थिरता / पठनीयता मुद्दा है, प्रदर्शन नहीं। संकलक सबसे अधिक संभावना यह संकलन समय पर करेगा, जिसे आप प्रदर्शन के लिए चाहते हैं।
पीटर कॉर्डेस

1
@PeterCordes आप सही हैं, अब मैं देख रहा हूं कि मेरा मन समाधान पर स्थिर था, और मैंने बाकी सब चीजों की अवहेलना की। किसी भी तरह से, जवाब रहता है, अगर केवल छायांकन के लिए। जब भी मुझे इसके बारे में संदेह होगा, मुझे पता होगा कि मुझे कहाँ देखना है।
एक संबंधित नागरिक

1
इस पर भी विचार करें कि Fraction f( x+y, a+b ); इसे अपने तरीके से लिखने के लिए, आपको BadFraction f( x+y, a+b, gcd(x+y, a+b) );tmp var लिखना या उपयोग करना होगा । या इससे भी बदतर, अगर आप लिखना चाहते हैं Fraction f( foo(x), bar(y) );- तो आपको रिटर्न मान रखने के लिए कॉल साइट की आवश्यकता होगी, कुछ टैम्प वेरिएंट्स की घोषणा करने के लिए, या उन कार्यों को फिर से कॉल करें और उम्मीद करें कि कंपाइलर सीएसई उन्हें दूर कर दें, जिसे हम टाल रहे हैं। क्या आप एक कॉलगर्ल के आर्ग के मिश्रण को डिबग करना चाहते हैं, gcdतो क्या यह वास्तव में कंस्ट्रक्टर को दिए गए पहले 2 आर्ग्स का GCD नहीं है? नहीं? फिर उस बग को संभव न बनाएं।
पीटर कॉर्डेस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.