दो शून्य-अर्ग कंस्ट्रक्टरों को अलग करने के लिए मुहावरेदार तरीका


41

मेरे पास एक वर्ग है:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    // more stuff

};

आमतौर पर मैं डिफ़ॉल्ट (शून्य) को दिखाना चाहता हूं countsजैसा कि दिखाया गया है।

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

इस तरह के "माध्यमिक" शून्य-अर्ग कंस्ट्रक्टर बनाने के लिए एक मुहावरेदार और कुशल तरीका क्या है?

वर्तमान में, मैं एक टैग क्लास का उपयोग कर रहा हूं, uninit_tagजिसे एक डमी तर्क के रूप में पारित किया गया है, जैसे:

struct uninit_tag{};

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    event_counts(uninit_tag) {}

    // more stuff

};

फिर मैं नो-इनिट कंस्ट्रक्टर को कॉल करता हूं जैसे event_counts c(uninit_tag{});मैं निर्माण को दबाना चाहता हूं।

मैं उन समाधानों के लिए खुला हूं जिनमें डमी वर्ग का निर्माण शामिल नहीं है, या किसी तरह से अधिक कुशल हैं, आदि।


"क्योंकि मुझे पता है कि सरणी ओवरराइट होने वाली है" क्या आप सुनिश्चित हैं कि आपका कंपाइलर आपके लिए पहले से ही उस अनुकूलन को नहीं कर रहा है? बिंदु में मामला: gcc.godbolt.org/z/bJnAuJ
फ्रैंक

6
@ फ्रेंक - मुझे लगता है कि आपके प्रश्न का उत्तर आपके द्वारा उद्धृत वाक्य के दूसरे भाग में है? यह प्रश्न से संबंधित नहीं है, लेकिन विभिन्न प्रकार की चीजें हो सकती हैं: (ए) अक्सर संकलक बस इतना मजबूत नहीं होता है कि वह मृत दुकानों को समाप्त कर सके (बी) कभी-कभी तत्वों का एक सबसेट अधिलेखित हो जाता है और यह अन्य को हरा देता है अनुकूलन (लेकिन केवल वही सबसेट बाद में पढ़ा जाता है) (ग) कभी-कभी कंपाइलर ऐसा कर सकता था, लेकिन इसे पराजित किया जाता है, क्योंकि विधि इनबिल्ड नहीं है।
BeeOnRope

क्या आपके पास अपनी कक्षा में कोई अन्य निर्माता हैं?
नेथनऑलिवर

1
@ फ्रेंक - एह, आपका मामला इंगित करता है कि जीसीसी मृत दुकानों को खत्म नहीं करता है ? वास्तव में, अगर आपने मुझे अनुमान लगाया था कि मैंने सोचा होगा कि जीसीसी को यह बहुत ही सरल मामला मिलेगा, लेकिन अगर यह यहां विफल रहता है तो किसी भी अधिक जटिल मामले की कल्पना करें!
BeeOnRope

1
@uneven_mark - हाँ, gcc 9.2 -O3 पर करता है (लेकिन यह अनुकूलन -O2, IME) की तुलना में असामान्य है, लेकिन पहले के संस्करण नहीं थे। सामान्य तौर पर, डेड स्टोर एलिमिनेशन एक चीज है, लेकिन यह बहुत ही नाजुक है और सभी सामान्य कैविट के अधीन है, जैसे कि कंपाइलर मृत दुकानों को एक ही समय में देखने के लिए सक्षम होने के नाते यह हावी दुकानों को देखता है। मेरी टिप्पणी यह ​​स्पष्ट करने के लिए अधिक थी कि फ्रैंक क्या कहना चाह रहे थे क्योंकि उन्होंने कहा था "मामले में: (गॉडबोल्ट लिंक)" लेकिन लिंक दोनों दुकानों को प्रदर्शन करते हुए दिखाता है (इसलिए शायद मैं कुछ याद कर रहा हूं)।
BeeOnRope

जवाबों:


33

आपके पास पहले से मौजूद समाधान सही है, और ठीक वही है जो मैं देखना चाहता हूं कि क्या मैं आपके कोड की समीक्षा कर रहा था। यह यथासंभव कुशल, स्पष्ट और संक्षिप्त है।


1
मेरे पास मुख्य मुद्दा यह है कि क्या मुझे uninit_tagहर जगह एक नए स्वाद की घोषणा करनी चाहिए जो मैं इस मुहावरे का उपयोग करना चाहता हूं। मैं उम्मीद कर रहा था कि पहले से ही इस तरह के एक संकेतक प्रकार की तरह कुछ था std::
BeeOnRope

9
मानक पुस्तकालय से कोई स्पष्ट विकल्प नहीं है। मैं हर वर्ग के लिए एक नया टैग परिभाषित नहीं करूंगा जहां मुझे यह सुविधा चाहिए - मैं एक परियोजना-व्यापी no_initटैग को परिभाषित करूंगा और अपनी सभी कक्षाओं में इसका उपयोग करूंगा जहां मुझे आवश्यकता है।
जॉन Zwinck

2
मुझे लगता है कि मानक पुस्तकालय में पुनरावृत्तियों और इस तरह के सामान और दो std::piecewise_construct_tऔर अलग करने के लिए मर्दाना टैग हैं std::in_place_t। उनमें से कोई भी यहां उपयोग करने के लिए उचित नहीं लगता है। हो सकता है कि आप हमेशा उपयोग करने के लिए अपने प्रकार की एक वैश्विक वस्तु को परिभाषित करना चाहते हों, इसलिए आपको हर कंस्ट्रक्टर कॉल में ब्रेस की आवश्यकता नहीं है। एसटीएल के साथ इस करता है std::piecewise_constructके लिए std::piecewise_construct_t
n314159

यह यथासंभव कुशल नहीं है। उदाहरण के लिए AArch64 कॉलिंग कन्वेंशन में टैग को स्टैक-आबंटित किया जाना है, जिसमें नॉक-ऑन इफेक्ट्स (टेल-कॉल भी नहीं हो सकते ...): godbolt.org/z/6mSsmq
TLW

1
@TLW एक बार जब आप कंस्ट्रक्टर्स के लिए बॉडी जोड़ लेते हैं तो स्टैक एलोकेशन नहीं होता है, godbolt.org/z/vkCD65
R2RT

8

यदि कंस्ट्रक्टर बॉडी खाली है, तो इसे छोड़ा या डिफ़ॉल्ट किया जा सकता है:

struct event_counts {
    std::uint64_t counts[MAX_COUNTERS];
    event_counts() = default;
};

फिर डिफ़ॉल्ट आरंभीकरण अनधिकृत रूपevent_counts counts; से छोड़ देगा counts.counts(डिफ़ॉल्ट इनिशियलाइज़ेशन यहां पर नो-ऑप है), और वैल्यू इनिशियलाइज़ेशन इनिशियलाइज़ेशन event_counts counts{}; को महत्व देगा counts.counts, इसे प्रभावी रूप से शून्य के साथ भरना।


3
लेकिन फिर आपको मूल्य आरंभीकरण का उपयोग करने के लिए याद रखना होगा और ओपी चाहता है कि यह डिफ़ॉल्ट रूप से सुरक्षित हो।
डॉक्टर

@ डॉक, मैं सहमत हूं। ओपी क्या चाहता है, यह सटीक समाधान नहीं है। लेकिन यह आरंभीकरण निर्मित प्रकारों की नकल करता है। क्योंकि int i;हम स्वीकार करते हैं कि यह शून्य-आरंभिक नहीं है। शायद हमें यह भी स्वीकार करना चाहिए कि event_counts counts;यह शून्य-आरंभीकृत नहीं है और event_counts counts{};हमारा नया डिफ़ॉल्ट बनाता है ।
Evg

6

मुझे आपका हल पसंद है। आप नेस्टेड स्ट्रक्चर और स्टैटिक वेरिएबल पर भी विचार कर सकते हैं। उदाहरण के लिए:

struct event_counts {
    static constexpr struct uninit_tag {} uninit = uninit_tag();

    uint64_t counts[MAX_COUNTS];

    event_counts() : counts{} {}

    explicit event_counts(uninit_tag) {}

    // more stuff

};

स्थिर वैरिएबल के साथ असिंचित निर्माणकर्ता कॉल अधिक सुविधाजनक लग सकता है:

event_counts e(event_counts::uninit);

आप निश्चित रूप से टाइपिंग को बचाने और इसे एक व्यवस्थित सुविधा के रूप में बनाने के लिए मैक्रो का परिचय दे सकते हैं

#define UNINIT_TAG static constexpr struct uninit_tag {} uninit = uninit_tag();

struct event_counts {
    UNINIT_TAG
}

struct other_counts {
    UNINIT_TAG
}

3

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

struct event_counts {
    enum Init { INIT, NO_INIT };
    uint64_t counts[MAX_COUNTERS];

    event_counts(Init init = INIT) {
        if (init == INIT) {
            std::fill(counts, counts + MAX_COUNTERS, 0);
        }
    }
};

तब उदाहरण बनाना इस तरह दिखता है:

event_counts e1{};
event_counts e2{event_counts::INIT};
event_counts e3{event_counts::NO_INIT};

या, इसे टैग वर्ग के दृष्टिकोण की तरह अधिक बनाने के लिए, टैग वर्ग के बजाय एकल-मान का उपयोग करें:

struct event_counts {
    enum NoInit { NO_INIT };
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}
    explicit event_counts(NoInit) {}
};

फिर एक उदाहरण बनाने के केवल दो तरीके हैं:

event_counts e1{};
event_counts e2{event_counts::NO_INIT};

मैं आपसे सहमत हूं: एनम सरल हैं। : लेकिन हो सकता है आप इस लाइन में भूल गया थाevent_counts() : counts{} {}
नीले

@ सुलझाओ, मेरा इरादा countsबिना शर्त शुरू करने का नहीं था , लेकिन केवल जब INITसेट किया जाता है।
तैमुक

@ मुझे लगता है कि टैग क्लास चुनने का मुख्य कारण सादगी को प्राप्त करना नहीं है, बल्कि यह संकेत देना है कि असमान वस्तु विशेष है, अर्थात यह क्लास इंटरफेस के सामान्य भाग के बजाय अनुकूलन सुविधा का उपयोग करती है। दोनों boolऔर enumसभ्य हैं, लेकिन हम जानते हैं कि अधिभार के बजाय पैरामीटर का उपयोग कर कुछ अलग semantical छाया है रहना होगा। पूर्व में आप स्पष्ट रूप से एक वस्तु को पराजित करते हैं, इसलिए आरंभिक / असिंचित रुख इसका राज्य बन जाता है, जबकि ctor में टैग ऑब्जेक्ट पास करना वर्ग को रूपांतरण करने के लिए कहने जैसा अधिक होता है। तो यह IMO वाक्य-विन्यास की पसंद की बात नहीं है।
doc

@TimK लेकिन ओपी चाहता है कि डिफ़ॉल्ट व्यवहार एरे का प्रारंभ हो, इसलिए मुझे लगता है कि प्रश्न के आपके समाधान में शामिल होना चाहिए event_counts() : counts{} {}
नीले

जब तक अनुरोध नहीं किया जाता countsहै, std::fillतब तक मेरे मूल सुझाव पर आरंभ करें NO_INIT। डिफ़ॉल्ट कंस्ट्रक्टर को जोड़ने के रूप में आप सुझाव देते हैं कि डिफ़ॉल्ट इनिशियलाइज़ेशन करने के दो अलग-अलग तरीके होंगे, जो एक महान विचार नहीं है। मैंने एक और दृष्टिकोण जोड़ा है जो प्रयोग करने से बचता है std::fill
टिमके

1

आप अपनी कक्षा के लिए दो-चरण के प्रारंभ पर विचार कर सकते हैं :

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() = default;

    void set_zero() {
       std::fill(std::begin(counts), std::end(counts), 0u);
    }
};

ऊपर दिया गया कंस्ट्रक्टर सरणी को शून्य पर आरंभीकृत नहीं करता है। सरणी के तत्वों को शून्य पर सेट करने के लिए, आपको set_zero()निर्माण के बाद सदस्य फ़ंक्शन को कॉल करना होगा ।


7
धन्यवाद, मैंने इस दृष्टिकोण पर विचार किया लेकिन कुछ ऐसा है जो डिफ़ॉल्ट को सुरक्षित रखता है - यानी, डिफ़ॉल्ट रूप से शून्य, और केवल कुछ चुनिंदा स्थानों पर मैं व्यवहार को असुरक्षित एक पर हावी करता हूं।
BeeOnRope

3
इसके लिए अतिरिक्त सावधानी बरतने की आवश्यकता होगी, लेकिन जिन उपयोगों को एकतरफा माना जाता है। तो यह OPs समाधान के सापेक्ष बग का एक अतिरिक्त स्रोत है।
अखरोट

@BeeOnRope एक डिफॉल्ट लॉजिक के std::functionसमान कुछ के साथ एक कंस्ट्रक्टर तर्क के रूप में भी प्रदान कर सकता set_zeroहै। यदि आप एकतरफा सरणी चाहते हैं तो आप एक लंबोतरा फ़ंक्शन पास करेंगे।
doc

1

मैं इसे इस तरह से करूंगा:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    event_counts(bool initCounts) {
        if (initCounts) {
            std::fill(counts, counts + MAX_COUNTERS, 0);
        }
    }
};

कंपाइलर आपके द्वारा उपयोग किए जाने पर सभी कोड को छोड़ने के लिए पर्याप्त स्मार्ट होगा event_counts(false), और आपको अपनी कक्षा के इंटरफ़ेस को इतना अजीब बनाने के बजाय कहने का मतलब है।


8
आप दक्षता के बारे में सही हैं, लेकिन बूलियन पैरामीटर पठनीय ग्राहक कोड के लिए नहीं बनाते हैं। जब आप साथ पढ़ रहे हैं, और आप घोषणा को देखते हैं event_counts(false), तो इसका क्या मतलब है? आपको वापस जाने और पैरामीटर के नाम को देखे बिना कोई विचार नहीं है । कम से कम एक enum का उपयोग करने के लिए बेहतर है, या, इस मामले में, एक प्रहरी / टैग वर्ग जैसा कि प्रश्न में दिखाया गया है। फिर, आपको एक घोषणा अधिक पसंद आती है event_counts(no_init), जो इसके अर्थ में सभी के लिए स्पष्ट है।
कोड़ी ग्रे

मुझे लगता है कि यह भी सभ्य समाधान है। आप डिफ़ॉल्ट ctor को त्याग सकते हैं और डिफ़ॉल्ट मान का उपयोग कर सकते हैं event_counts(bool initCountr = true)
डॉक्टर

साथ ही, ctor स्पष्ट होना चाहिए।
डॉक्टर

दुर्भाग्य से वर्तमान में सी ++ नामित मापदंडों का समर्थन नहीं करता है, लेकिन हम पठनीयता के लिए उपयोग boost::parameterऔर कॉल कर सकते हैंevent_counts(initCounts = false)
phuclv

1
मज़ेदार रूप से पर्याप्त, @doc, event_counts(bool initCounts = true)वास्तव में एक डिफ़ॉल्ट निर्माता है, जिसका हर मान डिफ़ॉल्ट मान होने के कारण होता है। आवश्यकता सिर्फ इतनी है कि यह तर्कों को निर्दिष्ट किए बिना कॉल करने योग्य हो, event_counts ec;परवाह नहीं करता है कि यह पैरामीटर-कम है या डिफ़ॉल्ट मानों का उपयोग करता है।
जस्टिन टाइम -

1

मैं थोड़ा टाइप बचाने के लिए एक उपवर्ग का उपयोग करूंगा:

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}
    event_counts(uninit_tag) {}
};    

struct event_counts_no_init: event_counts {
    event_counts_no_init(): event_counts(uninit_tag{}) {}
};

आप कंस्ट्रक्शन को इनिशियलाइज़ नहीं करने के तर्क से डमी क्लास से छुटकारा पा सकते हैं या कुछ और boolकर intसकते हैं, क्योंकि इसके लिए एमनेमिक होना जरूरी नहीं है।

आप विरासत को चारों ओर स्वैप कर सकते हैं और events_count_no_initएक डिफ़ॉल्ट निर्माणकर्ता के साथ परिभाषित कर सकते हैं जैसे Evg उनके उत्तर में सुझाए गए हैं, और फिर events_countउपवर्ग हो सकते हैं:

struct event_counts_no_init {
    uint64_t counts[MAX_COUNTERS];
    event_counts_no_init() = default;
};

struct event_counts: event_counts_no_init {
    event_counts(): event_counts_no_init{} {}
};

यह एक दिलचस्प विचार है, लेकिन मुझे यह भी लगता है कि एक नए प्रकार की शुरुआत करने से घर्षण पैदा होगा। उदाहरण के लिए, जब मैं वास्तव में एक असंगठित चाहता हूं, तो मैं चाहता event_countsहूं कि यह टाइप का हो event_count, न कि event_count_uninitialized, इसलिए मुझे निर्माण पर सही तरह से स्लाइस करना चाहिए event_counts c = event_counts_no_init{};, जो मुझे लगता है कि टाइपिंग में अधिकांश बचत को समाप्त कर देता है।
BeeOnRope

@BeeOnRope खैर, अधिकांश उद्देश्यों के लिए एक event_count_uninitializedवस्तु एक event_countवस्तु है। यह विरासत का पूरा बिंदु है, वे पूरी तरह से अलग प्रकार के नहीं हैं।
रॉस रिज

सहमत, लेकिन रगड़ "अधिकांश उद्देश्यों के लिए" है। वे विनिमेय नहीं हैं - उदाहरण के लिए, यदि आप इसे काम करने के लिए असाइन ecuकरने की कोशिश करते हैं ec, लेकिन चारों ओर नहीं। या यदि आप टेम्पलेट फ़ंक्शंस का उपयोग करते हैं, तो वे अलग-अलग प्रकार के होते हैं और अलग-अलग तात्कालिकता के साथ समाप्त होते हैं, भले ही व्यवहार समान हो (और कभी-कभी यह उदा नहीं होगा, स्थिर टेम्पलेट सदस्यों के साथ)। विशेष रूप से autoइस के भारी उपयोग से निश्चित रूप से फसल हो सकती है और भ्रमित हो सकती है: मैं नहीं चाहूंगा कि जिस तरह से किसी वस्तु को अपने प्रकार में स्थायी रूप से परिलक्षित किया गया था।
BeeOnRope
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.