बचने के लिए अगर लूप के अंदर बयान?


116

मेरे पास एक वर्ग है जिसे Writerएक फ़ंक्शन है writeVectorजैसे:

void Drawer::writeVector(vector<T> vec, bool index=true)
{
    for (unsigned int i = 0; i < vec.size(); i++) {
        if (index) {
            cout << i << "\t";
        }
        cout << vec[i] << "\n";
    }
}

मैं डुप्लिकेट कोड नहीं होने की कोशिश कर रहा हूं, जबकि प्रदर्शन के बारे में अभी भी चिंता है। फ़ंक्शन में, मैं if (index)अपने हर- forलूप पर चेक कर रहा हूं , भले ही परिणाम हमेशा समान हो। यह "प्रदर्शन के बारे में चिंता" के खिलाफ है।

मैं अपने forलूप के बाहर चेक रखकर आसानी से इससे बच सकता था । हालाँकि, मुझे डुप्लिकेट कोड का भार मिलेगा:

void Drawer::writeVector(...)
{
    if (index) {
        for (...) {
            cout << i << "\t" << vec[i] << "\n";
        }
    }
    else {
        for (...) {
            cout << vec[i] << "\n";
        }
    }
}

तो ये दोनों मेरे लिए "बुरे" समाधान हैं। जो मैं सोच रहा था, वह दो निजी कार्य हैं, उनमें से एक सूचकांक को बाहर करता है और फिर दूसरे को कॉल करता है। अन्य केवल मूल्य को बाहर करते हैं। हालाँकि, मैं यह पता नहीं लगा सकता कि इसे अपने कार्यक्रम के साथ कैसे उपयोग किया जाए, फिर भी मुझे यह ifदेखने के लिए चेक की आवश्यकता होगी कि किसे कॉल करना है ...

समस्या के अनुसार, बहुरूपता एक सही समाधान की तरह लगता है। लेकिन मैं यह नहीं देख सकता कि मुझे यहाँ कैसे उपयोग करना चाहिए। इस तरह की समस्या को हल करने का पसंदीदा तरीका क्या होगा?

यह एक वास्तविक कार्यक्रम नहीं है, मुझे सिर्फ यह सीखने में दिलचस्पी है कि इस तरह की समस्या को कैसे हल किया जाना चाहिए।


8
@JonathonReinhart शायद कुछ लोग प्रोग्रामिंग सीखना चाहते हैं, और समस्याओं को हल करने के लिए उत्सुक हैं?
स्केमाह वन

9
मैंने यह प्रश्न +1 दिया है। इस तरह का अनुकूलन अक्सर आवश्यक नहीं हो सकता है, लेकिन सबसे पहले, इस तथ्य को इंगित करना उत्तर का हिस्सा हो सकता है, और दूसरी बात, दुर्लभ प्रकार के अनुकूलन अभी भी प्रोग्रामिंग के लिए अत्यधिक प्रासंगिक हैं।
जोगोजापान

31
प्रश्न अच्छे डिजाइन के बारे में है जो लूप के अंदर कोड दोहराव और जटिल तर्क से बचा जाता है। यह एक अच्छा सवाल है, इसे कम करने की कोई जरूरत नहीं है।
अली

5
यह एक दिलचस्प सवाल है, आमतौर पर संकलक में लूप-परिवर्तन पास इसे बहुत कुशलता से हल करेंगे। यदि फ़ंक्शन पर्याप्त रूप से छोटा है, तो इनलाइनर इसके बारे में ध्यान रखेगा और संभवतः शाखा को पूरी तरह से मार देगा। जब तक इनलाइनर ख़ुशी से कोड को हल नहीं कर रहा है, तब तक मैं कोड को बदल दूंगा।
एलेक्स

5
@ जोंथोनरिनहार्ट: हुह? प्रश्न का पहला संशोधन लगभग इसी के समान है। आपका "आप क्यों परवाह करते हैं?" सभी संशोधनों के लिए टिप्पणी 100% अप्रासंगिक है । सार्वजनिक रूप से आपको फटकारने के लिए - यह सिर्फ आप नहीं है, यह बहुत से लोग हैं जो इस समस्या का कारण हैं। किया जाता है तो शीर्षक "अगर बयान पाश के लिए एक के अंदर से बचने" , यह बहुत स्पष्ट होना चाहिए कि प्रश्न सामान्य है, और उदाहरण सिर्फ उदाहरण के लिए है । आप किसी की मदद नहीं कर रहे हैं जब आप सवाल को नजरअंदाज करते हैं और ओपी को बेवकूफ बनाते हैं क्योंकि वह विशेष रूप से उदाहरण के लिए उपयोग करता है।
user541686

जवाबों:


79

लूप के शरीर में एक फफूंद के रूप में पास करें। यह संकलन-समय पर निष्प्रभावी हो जाता है, कोई प्रदर्शन दंड नहीं।

जो बदलता है उसमें गुजरने का विचार सी ++ स्टैंडर्ड लाइब्रेरी में सर्वव्यापी है। इसे रणनीति पैटर्न कहा जाता है

यदि आपको C ++ 11 का उपयोग करने की अनुमति है, तो आप कुछ इस तरह से कर सकते हैं:

#include <iostream>
#include <set>
#include <vector>

template <typename Container, typename Functor, typename Index = std::size_t>
void for_each_indexed(const Container& c, Functor f, Index index = 0) {

    for (const auto& e : c)
        f(index++, e);
}

int main() {

    using namespace std;

    set<char> s{'b', 'a', 'c'};

    // indices starting at 1 instead of 0
    for_each_indexed(s, [](size_t i, char e) { cout<<i<<'\t'<<e<<'\n'; }, 1u);

    cout << "-----" << endl;

    vector<int> v{77, 88, 99};

    // without index
    for_each_indexed(v, [](size_t , int e) { cout<<e<<'\n'; });
}

यह कोड सही नहीं है, लेकिन आपको यह विचार मिलता है।

पुराने C ++ 98 में यह इस तरह दिखता है:

#include <iostream>
#include <vector>
using namespace std;

struct with_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << i << '\t' << e << '\n';
  }
};

struct without_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << e << '\n';
  }
};


template <typename Func>
void writeVector(const vector<int>& v, Func f) {
  for (vector<int>::size_type i=0; i<v.size(); ++i) {
    f(cout, i, v[i]);
  }
}

int main() {

  vector<int> v;
  v.push_back(77);
  v.push_back(88);
  v.push_back(99);

  writeVector(v, with_index());

  cout << "-----" << endl;

  writeVector(v, without_index());

  return 0;
}

फिर से, कोड एकदम सही है लेकिन यह आपको विचार देता है।


4
for(int i=0;i<100;i++){cout<<"Thank you!"<<endl;}: D यह एक ऐसा समाधान है जिसकी मैं तलाश कर रहा था, यह एक आकर्षण की तरह काम करता है :) आप इसे कुछ टिप्पणियों के साथ बेहतर बना सकते थे (पहली बार में इसे समझने में समस्याएँ थीं), लेकिन मुझे इसकी कोई समस्या नहीं मिली :)
Skamah One

1
मुझे खुशी है कि इसने मदद की! कृपया C ++ 11 कोड के साथ मेरे अपडेट की जांच करें, यह C ++ 98 संस्करण की तुलना में कम फूला हुआ है।
अली

3
नाइटपिक: यह ओपी के उदाहरण के मामले में ठीक है क्योंकि लूप बॉडी इतनी छोटी है, लेकिन अगर यह बड़ा था (केवल एक एकल के बजाय एक दर्जन लाइनों की कोड की कल्पना करें cout << e << "\n";) अभी भी काफी कुछ कोड दोहराव होगा।
syam

3
संरचनाएं और ऑपरेटर ओवरलोडिंग का उपयोग C ++ 03 उदाहरण में क्यों किया जाता है? क्यों न केवल दो कार्य किए जाएं और उन्हें संकेत दिए जाएं?
मैल्कम

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

40

फ़ंक्शन में, मैं अपने फॉर-लूप के प्रत्येक राउंड पर if (इंडेक्स) चेक कर रहा हूं, भले ही परिणाम हमेशा एक ही हो। यह "प्रदर्शन के बारे में चिंता" के खिलाफ है।

यदि यह वास्तव में मामला है, तो शाखा भविष्यवक्ता को (निरंतर) परिणाम की भविष्यवाणी करने में कोई समस्या नहीं होगी। जैसे, यह केवल पहले कुछ पुनरावृत्तियों में गलतफहमी के लिए एक हल्के ओवरहेड का कारण होगा। प्रदर्शन के मामले में चिंता की कोई बात नहीं है

इस मामले में मैं स्पष्टता के लिए लूप के अंदर परीक्षण रखने की वकालत करता हूं।


3
यह सिर्फ एक उदाहरण है, मैं यहां यह जानने के लिए हूं कि इस तरह की समस्या को कैसे हल किया जाना चाहिए। मैं सिर्फ उत्सुक हूं, एक वास्तविक कार्यक्रम भी नहीं बना रहा हूं। प्रश्न में इसका उल्लेख किया जाना चाहिए।
स्केमह वन

40
उस मामले में, ध्यान रखें कि समय से पहले अनुकूलन सभी बुराई की जड़ है । प्रोग्रामिंग करते समय, हमेशा कोड पठनीयता पर ध्यान केंद्रित करें और सुनिश्चित करें कि अन्य समझें कि आप क्या करने की कोशिश कर रहे हैं। केवल अपने कार्यक्रम की रूपरेखा तैयार करने और हॉटस्पॉट की पहचान करने के बाद माइक्रो-ऑप्टिमाइज़ेशन और विभिन्न हैक पर विचार करें । उनके लिए आवश्यकता की स्थापना के बिना आपको कभी भी अनुकूलन का विचार नहीं करना चाहिए। बहुत बार, प्रदर्शन समस्याएं ऐसी नहीं होती हैं जहाँ आप उनसे उम्मीद करते हैं।
मार्क क्लेसेन

3
और इस विशेष उदाहरण में (ठीक है, समझे, यह सिर्फ एक उदाहरण है) यह बहुत संभावना है कि लूप नियंत्रण के लिए बिताया गया समय और यदि परीक्षण IO के लिए बिताए समय के लगभग अदृश्य है। यह अक्सर C ++ के साथ एक मुद्दा है: रखरखाव की लागत और पठनीयता (दक्षता) की लागत पर पठनीयता के बीच चयन करना।
क्रि।

8
आप मान रहे हैं कि कोड एक ऐसे प्रोसेसर पर चल रहा है जिसकी शुरुआत करने के लिए शाखा की भविष्यवाणी है। C ++ चलाने वाले अधिकांश सिस्टम नहीं हैं। (हालांकि, शायद एक उपयोगी के साथ सिस्टम के बहुमत std::cout)
बेन Voigt

2
-1। हां, शाखा की भविष्यवाणी यहां अच्छी तरह से काम करेगी। हां, कंपाइलर द्वारा शर्त को वास्तव में लूप के बाहर फहराया जा सकता है। जी हां, POITROAE। लेकिन एक पाश के भीतर शाखाओं हैं एक खतरनाक बात यह है कि अक्सर प्रदर्शन प्रभाव पड़ता है अगर किसी को वास्तव में चिंताओं प्रदर्शन के बारे में एक अच्छी सलाह है, और मैं सिर्फ "शाखा भविष्यवाणी" कह कर उन को खारिज नहीं लगता कि है। सबसे उल्लेखनीय उदाहरण यह है कि एक वेक्टर संकलक को इसे संभालने के लिए पूर्वानुमान की आवश्यकता होगी , जो शाखा-रहित छोरों की तुलना में कम कुशल कोड का उत्पादन करेगा ।
ओक

35

अली के उत्तर पर विस्तार करने के लिए, जो पूरी तरह से सही है, लेकिन फिर भी कुछ कोड (लूप बॉडी का हिस्सा) की नकल करता है, यह दुर्भाग्य से रणनीति पैटर्न का उपयोग करते समय शायद ही परिहार्य है ...

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

कुंजी संकलक की क्षमता का उपयोग लगातार तह / मृत कोड उन्मूलन करने के लिए है । हम यह कर सकते हैं कि मैन्युअल रूप से indexसंकलन-समय मान के रनटाइम मूल्य को मैप करना (आसान है जब केवल सीमित संख्या में मामले हों - इस मामले में दो) और एक गैर-प्रकार टेम्पलेट तर्क का उपयोग करें जो संकलन में जाना जाता है -समय:

template<bool index = true>
//                  ^^^^^^ note: the default value is now part of the template version
//                         see below to understand why
void writeVector(const vector<int>& vec) {
    for (size_t i = 0; i < vec.size(); ++i) {
        if (index) { // compile-time constant: this test will always be eliminated
            cout << i << "\t"; // this will only be kept if "index" is true
        }
        cout << vec[i] << "\n";
    }
}

void writeVector(const vector<int>& vec, bool index)
//                                            ^^^^^ note: no more default value, otherwise
//                                            it would clash with the template overload
{
    if (index) // runtime decision
        writeVector<true>(vec);
        //          ^^^^ map it to a compile-time constant
    else
        writeVector<false>(vec);
}

इस तरह से हम संकलित कोड के साथ समाप्त होते हैं, जो आपके दूसरे कोड उदाहरण (बाहरी if/ आंतरिक for) के बराबर है, लेकिन कोड को दोहराए बिना। अब हम writeVectorजितना चाहें उतना टेम्प्लेट संस्करण बना सकते हैं, जिसे बनाए रखने के लिए हमेशा एक ही कोड होगा।

ध्यान दें कि कैसे टेम्पलेट संस्करण (जो एक गैर-प्रकार टेम्पलेट तर्क के रूप में एक संकलन-समय स्थिर लेता है) और गैर-टेम्पलेट संस्करण (जो फ़ंक्शन तर्क के रूप में रनटाइम चर लेता है) अतिभारित हैं। यह आपको अपनी आवश्यकताओं के आधार पर सबसे अधिक प्रासंगिक संस्करण चुनने की अनुमति देता है, बल्कि दोनों मामलों में वाक्यविन्यास को याद रखना आसान है:

writeVector<true>(vec);   // you already know at compile-time which version you want
                          // no need to go through the non-template runtime dispatching

writeVector(vec, index);  // you don't know at compile-time what "index" will be
                          // so you have to use the non-template runtime dispatching

writeVector(vec);         // you can even use your previous syntax using a default argument
                          // it will call the template overload directly

2
कृपया ध्यान रखें कि आपने लूप के अंदर तर्क को और अधिक जटिल बनाने की कीमत पर कोड दोहराव को हटा दिया। मैं इसे न तो बेहतर देखता हूं और न ही इससे ज्यादा बुरा, जो मैंने इस विशेष सरल उदाहरण के लिए प्रस्तावित किया है। +1 वैसे भी!
अली

1
मुझे आपका प्रस्ताव पसंद है क्योंकि यह एक और संभावित अनुकूलन दर्शाता है। यह बहुत संभव है कि सूचकांक शुरू से ही एक स्थिर टेम्पलेट हो सकता है। इस मामले में इसे राइट-वी के कॉलर द्वारा रनटाइम स्थिरांक से बदला जा सकता है और राइटवेक्टर को कुछ टेम्प्लेट में बदल दिया जाता है। मूल कोड में किसी और बदलाव से बचना।
क्रिश

1
@kriss: वास्तव में मेरे पिछले समाधान ने पहले ही अनुमति दे दी थी कि अगर आप doWriteVectorसीधे फोन करते हैं लेकिन मैं मानता हूं कि नाम दुर्भाग्यपूर्ण था। मैंने इसे केवल दो अतिभारित writeVectorकार्यों (एक टेम्पलेट, एक नियमित फ़ंक्शन) के लिए बदल दिया ताकि परिणाम अधिक सजातीय हो। सलाह के लिये धन्यवाद। ;)
स्याम

4
IMO यह सबसे अच्छा जवाब है। +1
user541686

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

0

ज्यादातर मामलों में, आपका कोड प्रदर्शन और पठनीयता के लिए पहले से ही अच्छा है। एक अच्छा संकलक लूप आक्रमणकारियों का पता लगाने और उपयुक्त अनुकूलन करने में सक्षम है। निम्नलिखित उदाहरण पर विचार करें जो आपके कोड के बहुत करीब है:

#include <cstdio>
#include <iterator>

void write_vector(int* begin, int* end, bool print_index = false) {
    unsigned index = 0;
    for(int* it = begin; it != end; ++it) {
        if (print_index) {
            std::printf("%d: %d\n", index, *it);
        } else {
            std::printf("%d\n", *it);
        }
        ++index;
    }
}

int my_vector[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
};


int main(int argc, char** argv) {
    write_vector(std::begin(my_vector), std::end(my_vector));
}

इसे संकलित करने के लिए मैं निम्नलिखित कमांड लाइन का उपयोग कर रहा हूं:

g++ --version
g++ (GCC) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
g++ -O3 -std=c++11 main.cpp

फिर, चलो विधानसभा को डंप करें:

objdump -d a.out | c++filt > main.s

का परिणाम विधानसभा write_vectorहै:

00000000004005c0 <write_vector(int*, int*, bool)>:
  4005c0:   48 39 f7                cmp    %rsi,%rdi
  4005c3:   41 54                   push   %r12
  4005c5:   49 89 f4                mov    %rsi,%r12
  4005c8:   55                      push   %rbp
  4005c9:   53                      push   %rbx
  4005ca:   48 89 fb                mov    %rdi,%rbx
  4005cd:   74 25                   je     4005f4 <write_vector(int*, int*, bool)+0x34>
  4005cf:   84 d2                   test   %dl,%dl
  4005d1:   74 2d                   je     400600 <write_vector(int*, int*, bool)+0x40>
  4005d3:   31 ed                   xor    %ebp,%ebp
  4005d5:   0f 1f 00                nopl   (%rax)
  4005d8:   8b 13                   mov    (%rbx),%edx
  4005da:   89 ee                   mov    %ebp,%esi
  4005dc:   31 c0                   xor    %eax,%eax
  4005de:   bf a4 06 40 00          mov    $0x4006a4,%edi
  4005e3:   48 83 c3 04             add    $0x4,%rbx
  4005e7:   83 c5 01                add    $0x1,%ebp
  4005ea:   e8 81 fe ff ff          callq  400470 <printf@plt>
  4005ef:   49 39 dc                cmp    %rbx,%r12
  4005f2:   75 e4                   jne    4005d8 <write_vector(int*, int*, bool)+0x18>
  4005f4:   5b                      pop    %rbx
  4005f5:   5d                      pop    %rbp
  4005f6:   41 5c                   pop    %r12
  4005f8:   c3                      retq   
  4005f9:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
  400600:   8b 33                   mov    (%rbx),%esi
  400602:   31 c0                   xor    %eax,%eax
  400604:   bf a8 06 40 00          mov    $0x4006a8,%edi
  400609:   48 83 c3 04             add    $0x4,%rbx
  40060d:   e8 5e fe ff ff          callq  400470 <printf@plt>
  400612:   49 39 dc                cmp    %rbx,%r12
  400615:   75 e9                   jne    400600 <write_vector(int*, int*, bool)+0x40>
  400617:   eb db                   jmp    4005f4 <write_vector(int*, int*, bool)+0x34>
  400619:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)

हम देख सकते हैं, कि फ़ंक्शन के भीख मांगने पर, हम मान की जाँच करते हैं और दो संभावित छोरों में से एक पर कूदते हैं:

  4005cf:   84 d2                   test   %dl,%dl
  4005d1:   74 2d                   je     400600 <write_vector(int*, int*, bool)+0x40>

बेशक, यह केवल तभी काम करता है जब एक कंपाइलर यह पता लगाने में सक्षम हो कि एक शर्त वास्तविक अपरिवर्तनीय है। आमतौर पर, यह झंडे और सरल इनलाइन कार्यों के लिए पूरी तरह से काम करता है। लेकिन अगर हालत "जटिल" है, तो अन्य उत्तरों से दृष्टिकोण का उपयोग करने पर विचार करें।

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