एक शॉट 'अगर' लिखने के लिए सबसे सुंदर तरीका


136

चूंकि C ++ 17 एक ifब्लॉक लिख सकता है जो इस तरह से एक बार निष्पादित हो जाएगा:

#include <iostream>
int main() {
    for (unsigned i = 0; i < 10; ++i) {

        if (static bool do_once = true; do_once) { // Enter only once
            std::cout << "hello one-shot" << std::endl;
            // Possibly much more code
            do_once = false;
        }

    }
}

मुझे पता है कि मैं इसे उखाड़ फेंक सकता हूं, और इसे हल करने के अन्य तरीके भी हैं, लेकिन फिर भी - क्या यह इस तरह से किसी भी तरह से लिखना संभव है, इसलिए do_once = falseअंत में कोई आवश्यकता नहीं है ?

if (DO_ONCE) {
    // Do stuff
}

मैं एक सहायक कार्य सोच रहा हूँ do_once(), जिसमें शामिल है static bool do_once, लेकिन क्या होगा अगर मैं अलग-अलग स्थानों में उसी फ़ंक्शन का उपयोग करना चाहता हूं? यह समय और जगह के लिए एक हो सकता है #define? मुझे आशा नहीं है।


52
सिर्फ क्यों नहीं if (i == 0)? यह काफी स्पष्ट है।
सिल्वानोकेर्ज़ा

26
@SilvanoCerza क्योंकि वह बात नहीं है। यह अगर ब्लॉक किसी फंक्शन में कहीं हो सकता है जो कई बार निष्पादित हो जाता है, न कि एक नियमित लूप में
नाडा

8
शायद std::call_onceएक विकल्प है (यह थ्रेडिंग के लिए उपयोग किया जाता है, लेकिन फिर भी यह काम करता है)।
fdan

25
आपका उदाहरण आपकी वास्तविक दुनिया की समस्या का एक बुरा प्रतिबिंब हो सकता है, जिसे आप हमें नहीं दिखा रहे हैं, लेकिन सिर्फ कॉल-वॉन फ़ंक्शन को लूप से बाहर क्यों नहीं उठाएं?
रबनेव

14
यह मेरे लिए नहीं था कि एक ifस्थिति में आरंभिक चर हो सकते हैं static। यह चालाकी है।
होलीबैक्कट

जवाबों:


143

उपयोग करें std::exchange:

if (static bool do_once = true; std::exchange(do_once, false))

आप इसे सच मान के उलट कर सकते हैं:

if (static bool do_once; !std::exchange(do_once, true))

लेकिन अगर आप इसका भरपूर उपयोग कर रहे हैं, तो कल्पना न करें और इसकी जगह एक आवरण बनाएं:

struct Once {
    bool b = true;
    explicit operator bool() { return std::exchange(b, false); }
};

और इसका उपयोग करें:

if (static Once once; once)

चर को शर्त के बाहर संदर्भित नहीं किया जाना चाहिए, इसलिए नाम हमें अधिक नहीं खरीदता है। पायथन जैसी अन्य भाषाओं से प्रेरणा लेना जो _पहचानकर्ता को एक विशेष अर्थ देते हैं, हम लिख सकते हैं:

if (static Once _; _)

आगे के सुधार: बीएसएस अनुभाग (@ डेडप्लिकेटर) का लाभ उठाएं, मेमोरी लिखने से बचें, जब हम पहले से ही (@ शादो रेंजर) चला चुके हैं, और एक शाखा भविष्यवाणी संकेत दें, यदि आप कई बार परीक्षा दे रहे हैं (जैसे प्रश्न में):

// GCC, Clang, icc only; use [[likely]] in C++20 instead
#define likely(x) __builtin_expect(!!(x), 1)

struct Once {
    bool b = false;
    explicit operator bool()
    {
        if (likely(b))
            return false;

        b = true;
        return true;
    }
};

32
मुझे पता है कि मैक्रोज़ को C ++ में बहुत नफरत मिलती है, लेकिन यह सिर्फ इतना साफ दिखता है: के #define ONLY_ONCE if (static bool DO_ONCE_ = true; std::exchange(DO_ONCE_, false))रूप में इस्तेमाल किया जाना है:ONLY_ONCE { foo(); }
फाइबल्स

5
मेरा मतलब है, यदि आप तीन बार "एक बार" लिखते हैं, तो इसे तीन बार से अधिक उपयोग करें यदि बयानों में यह इसके लायक है
एलन

13
नाम _का उपयोग बहुत सारे सॉफ़्टवेयर में किया जाता है ताकि अनुवाद योग्य तार को चिह्नित किया जा सके। दिलचस्प बातें होने की उम्मीद है।
साइमन रिक्टर

1
यदि आपके पास विकल्प है, तो स्थिर स्थिति का प्रारंभिक मान ऑल-बिट्स-शून्य होना पसंद करें। अधिकांश निष्पादन योग्य-प्रारूपों में एक सभी-शून्य क्षेत्र की लंबाई होती है।
डेडुप्लिकेटर

7
_चर का उपयोग करने के लिए संयुक्त राष्ट्र Pythonic होगा। आप का उपयोग नहीं करते _चर कि बाद में केवल दुकान मूल्यों को संदर्भित किया जाएगा, जहाँ आप के लिए है एक चर प्रदान करने के लिए, लेकिन आप मूल्य की जरूरत नहीं है। यह आमतौर पर unpacking के लिए उपयोग किया जाता है जब आपको केवल कुछ मूल्यों की आवश्यकता होती है। (अन्य उपयोग के मामले हैं, लेकिन वे
थ्रो

91

शायद सबसे सुरुचिपूर्ण समाधान नहीं है और आपको कोई वास्तविक नहीं दिखता है if, लेकिन मानक पुस्तकालय वास्तव में इस मामले को कवर करता है :, देखें std::call_once

#include <mutex>

std::once_flag flag;

for (int i = 0; i < 10; ++i)
    std::call_once(flag, [](){ std::puts("once\n"); });

यहाँ लाभ यह है कि यह धागा सुरक्षित है।


3
मुझे उस संदर्भ में std :: call_once की जानकारी नहीं थी। लेकिन इस समाधान के साथ आपको एक std घोषित करने की आवश्यकता है: एक बार आप उस स्थान का उपयोग करने वाले प्रत्येक स्थान के लिए एक बार :: call_once, क्या आप नहीं?
नाद

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

9
@MichaelChourdakis मैं आपसे सहमत हूं, यह एक ओवरकिल है। हालांकि, इसके बारे में जानने लायक है, और विशेष रूप से व्यक्त करने की संभावना के बारे में जानना कि आप क्या कर रहे हैं ("इस कोड को एक बार निष्पादित करें") इसके बजाय कम पठनीय अगर-ट्रिकरी के पीछे कुछ छिपाने के लिए।
lubgr

17
उह, call_onceमुझे देखने का मतलब है कि आप एक बार कुछ कॉल करना चाहते हैं। पागल, मुझे पता है।
बैरी

3
@SergeyA क्योंकि यह आंतरिक तुल्यकालन का उपयोग करता है - सभी कुछ के लिए जो एक सादे द्वारा हल किया जाएगा यदि। जो कुछ मांगा गया था, उसके अलावा कुछ और करने का यह एक मुहावरेदार तरीका है।
को ऑर्बिट

52

C ++ में एक बिलियन कंट्रोल फ्लो आदिम है जो पहले से ही "( पहले-ब्लॉक; स्थिति; आफ्टर-ब्लॉक )" से युक्त है :

for (static bool b = true; b; b = false)

या हैकर, लेकिन कम:

for (static bool b; !b; b = !b)

हालांकि, मुझे लगता है कि यहां प्रस्तुत किसी भी तकनीक का उपयोग देखभाल के साथ किया जाना चाहिए, क्योंकि वे बहुत सामान्य नहीं हैं (अभी तक?)।


1
मुझे पहला विकल्प पसंद है (हालांकि - यहां कई प्रकारों की तरह - यह थ्रेड सुरक्षित नहीं है, इसलिए सावधान रहें)। दूसरा विकल्प मुझे (कठिन ठंड लगना पढ़ने के लिए और केवल दो धागे के साथ कितनी भी बार निष्पादित किया जा सकता देता है ... b == false: Thread 1मूल्यांकन करता है !bऔर पाश, के लिए प्रवेश करती Thread 2मूल्यांकन करता है !bऔर पाश के लिए प्रवेश करती है, Thread 1की स्थापना, पाश के लिए अपने सामान और पत्तियों करता है b == falseकरने के लिए !bयानी b = true... Thread 2अपने सामान और पत्तियों पाश के लिए, की स्थापना करता है b == trueकरने के लिए !bयानी b = falseपूरी प्रक्रिया की अनुमति अनिश्चित काल के दोहराया जाना)
CharonX

3
मुझे यह विडंबना लगता है कि किसी समस्या का सबसे सुरुचिपूर्ण समाधान, जहां कुछ कोड को केवल एक बार निष्पादित किया जाना चाहिए, एक लूप है । +1
नाडा

2
मैं बचूंगा b=!b, यह अच्छा लग रहा है, लेकिन आप वास्तव में चाहते हैं कि मूल्य गलत हो, इसलिए b=falseइसे प्राथमिकता दी जाएगी।
यो '

2
ध्यान रखें कि संरक्षित ब्लॉक फिर से चलेगा यदि यह गैर-स्थानीय रूप से बाहर निकलता है। यह वांछनीय भी हो सकता है, लेकिन यह अन्य सभी दृष्टिकोणों से अलग है।
डेविस हेरिंग

29

C ++ 17 में आप लिख सकते हैं

if (static int i; i == 0 && (i = 1)){

iलूप बॉडी में साथ खेलने से बचने के लिए । i0 के साथ शुरू होता है (मानक की गारंटी), और के बाद अभिव्यक्ति ;सेट iकरने के लिए 1पहली बार यह मूल्यांकन किया जाता है।

ध्यान दें कि C ++ 11 में आप लैम्बडा फ़ंक्शन के साथ समान प्राप्त कर सकते हैं

if ([]{static int i; return i == 0 && (i = 1);}()){

जो इसमें मामूली लाभ उठाता iहै, वह लूप बॉडी में लीक नहीं होता है।


4
मैं कहना है उदास हूँ - कि एक #define बुलाया CALL_ONCE में रखा जाना चाहिए, तो या तो यह अधिक पठनीय होगा
नाडा

9
हालांकि, static int i;(मैं वास्तव में यकीन नहीं कर रहा हूं) उन मामलों में से एक हो सकता है, जहां iइसे शुरू करने की गारंटी दी जाती है 0, यह static int i = 0;यहां उपयोग करने के लिए बहुत स्पष्ट है।
काइल विलमॉन

7
भले ही मैं मानता हूँ कि एक
शुरुआती

5
@ बाथेबा काइल ने ऐसा नहीं किया, इसलिए आपका दावा पहले ही झूठा साबित हो चुका है। स्पष्ट कोड के लिए दो वर्ण जोड़ने के लिए आपको क्या खर्च करना होगा? चलो, तुम एक "मुख्य सॉफ्टवेयर वास्तुकार" हो; आपको यह पता होना चाहिए :)
लाइटवेट दौड़ ऑर्बिट

5
यदि आपको लगता है कि एक चर के लिए प्रारंभिक मूल्य वर्तनी से स्पष्ट है, या सुझाव देता है कि "कुछ फंकी हो रहा है", मुझे नहीं लगता कि आपकी मदद की जा सकती है;)
को ऑर्बिट

14
static bool once = [] {
  std::cout << "Hello one-shot\n";
  return false;
}();

यह समाधान थ्रेड सुरक्षित है (कई अन्य सुझावों के विपरीत)।


3
क्या आप जानते हैं कि ()लैम्बडा घोषणा पर वैकल्पिक (यदि खाली है)?
नॉनमाय जूल

9

आप एक स्थिर ऑब्जेक्ट के निर्माण में एक बार की कार्रवाई को लपेट सकते हैं जो आप सशर्त के स्थान पर तुरंत करते हैं।

उदाहरण:

#include <iostream>
#include <functional>

struct do_once {
    do_once(std::function<void(void)> fun) {
        fun();
    }
};

int main()
{
    for (int i = 0; i < 3; ++i) {
        static do_once action([](){ std::cout << "once\n"; });
        std::cout << "Hello World\n";
    }
}

या आप वास्तव में मैक्रो के साथ चिपक सकते हैं, जो कुछ इस तरह दिख सकता है:

#include <iostream>

#define DO_ONCE(exp) \
do { \
  static bool used_before = false; \
  if (used_before) break; \
  used_before = true; \
  { exp; } \
} while(0)  

int main()
{
    for (int i = 0; i < 3; ++i) {
        DO_ONCE(std::cout << "once\n");
        std::cout << "Hello World\n";
    }
}

8

जैसे @ कदामन ने कहा, आप std::exchangeएक पूर्णांक पूर्णांक का उपयोग करके उपयोग करने से बच सकते हैं , लेकिन आपको याद रखना होगा कि नकारात्मक मूल्य सत्य का समाधान करते हैं। इसका उपयोग करने का तरीका होगा:

if (static int n_times = 3; n_times && n_times--)
{
    std::cout << "Hello world x3" << std::endl;
} 

@ एसोर्न के फैंसी रैपर में इसका अनुवाद इस तरह होगा:

struct n_times {
    int n;
    n_times(int number) {
        n = number;
    };
    explicit operator bool() {
        return n && n--;
    };
};

...

if(static n_times _(2); _)
{
    std::cout << "Hello world twice" << std::endl;
}

7

std::exchange@Acorn द्वारा सुझाए गए तरीके का उपयोग करते हुए , संभवतः सबसे मुहावरेदार तरीका है, विनिमय ऑपरेशन जरूरी नहीं है कि सस्ता हो। यद्यपि निश्चित रूप से स्थैतिक आरम्भिकता को थ्रेड-सुरक्षित होने की गारंटी दी जाती है (जब तक कि आप अपने कंपाइलर को ऐसा नहीं करने के लिए कहते हैं), इसलिए प्रदर्शन के बारे में कोई भी विचार staticकीवर्ड की मौजूदगी में कुछ हद तक निरर्थक हैं ।

यदि आप माइक्रो-ऑप्टिमाइज़ेशन के बारे में चिंतित हैं (जैसा कि C ++ अक्सर उपयोग करने वाले लोग हैं), तो आप इसके बजाय अच्छी तरह से खरोंच कर सकते हैं boolऔर उपयोग कर सकते हैं int, जो आपको पोस्ट-डिक्रीमेंट (या वृद्धि , वेतन वृद्धि) का उपयोग करने की अनुमति देगा , क्योंकि boolडिक्रिप्टिंग के विपरीत एक शून्य पर संतृप्त नहींint होगा ...):

if(static int do_once = 0; !do_once++)

ऐसा हुआ करता boolथा कि वेतन वृद्धि / वेतन वृद्धि ऑपरेटर थे, लेकिन वे बहुत पहले ही पदावनत हो गए थे (सी ++ + सुनिश्चित नहीं?) और पूरी तरह से सी ++ 17 में हटाए जाने थे। फिर भी आप एक intठीक जुर्म को कम कर सकते हैं, और यह निश्चित रूप से बूलियन स्थिति के रूप में काम करेगा।

बोनस: आप इसे लागू कर सकते हैं do_twiceया do_thriceइसी तरह ...


मैंने इसका परीक्षण किया और यह पहली बार को छोड़कर कई बार आग उगलता है।
नाडा

@ नादा: मूर्खतापूर्ण मुझे, तुम सही हो ... इसे सही किया। यह boolएक समय में एक बार साथ काम करता था और घटता था। लेकिन वेतन वृद्धि के साथ ठीक काम करता है int। ऑनलाइन डेमो देखें: coliru.stacked-croched.com/a/ee83f677a9c0c85a
डेमन

1
यह अभी भी समस्या है कि इसे कई बार निष्पादित किया जा सकता है जो do_onceचारों ओर लपेटता है और अंततः 0 पर फिर से (और बार-बार ...) मारा जाएगा।
नाडा

अधिक सटीक होने के लिए: यह अब प्रत्येक INT_MAX बार निष्पादित हो जाएगा।
नाद

ठीक है, लेकिन लूप काउंटर के अलावा भी उस मामले में चारों ओर लपेटना, यह शायद ही एक समस्या है। कुछ लोगों को 2 बिलियन (या 4 बिलियन अगर अहस्ताक्षरित) बिल्कुल भी किसी चीज की पुनरावृत्तियों से चलता है। यदि वे करते हैं, तब भी वे 64-बिट पूर्णांक का उपयोग कर सकते हैं। सबसे तेजी से उपलब्ध कंप्यूटर का उपयोग करते हुए, आप इसे चारों ओर लपेटने से पहले मर जाएंगे, ताकि आप इसके लिए मुकदमा न कर सकें।
डेमन

4

इसके लिए @ बतशेबा के शानदार जवाब के आधार पर - बस इसे और भी सरल बना दिया।

में C++ 17, आप बस कर सकते हैं:

if (static int i; !i++) {
  cout << "Execute once";
}

(पिछले संस्करणों में, बस int iब्लॉक के बाहर घोषित करें । सी :) में भी काम करता है)।

सरल शब्दों में: आप i की घोषणा करते हैं, जो कि शून्य का डिफ़ॉल्ट मान लेता है ( 0)। शून्य गलत है, इसलिए हम !इसे नकारने के लिए विस्मयादिबोधक चिह्न ( ) ऑपरेटर का उपयोग करते हैं। हम तब <ID>++ऑपरेटर की वेतन वृद्धि को ध्यान में रखते हैं , जो पहले संसाधित (असाइन, आदि) हो जाती है और फिर बढ़ जाती है।

इसलिए, इस ब्लॉक में, मुझे इनिशियलाइज़ किया जाएगा और वैल्यू 0केवल एक बार होगी, जब ब्लॉक निष्पादित हो जाएगा, और फिर मूल्य बढ़ जाएगा। हम !इसे नकारने के लिए बस ऑपरेटर का उपयोग करते हैं ।


1
यदि यह पहले पोस्ट किया गया होता, तो यह शायद अब स्वीकृत उत्तर होगा। बहुत अच्छे धन्यवाद!
नाडा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.