क्या कुल आरंभ सदस्यों के चूक को रोकना संभव है?


43

मेरे पास एक ही प्रकार के कई सदस्यों के साथ एक संरचना है, जैसे यह

struct VariablePointers {
   VariablePtr active;
   VariablePtr wasactive;
   VariablePtr filename;
};

समस्या यह है कि अगर मैं wasactiveइस तरह से एक संरचना के सदस्यों (जैसे ) को इनिशियलाइज़ करना भूल जाता हूँ :

VariablePointers{activePtr, filename}

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

कृपया C ++ 11 उत्तर भी जोड़ें , यदि C ++ 11 का समाधान है (वर्तमान में मैं उस संस्करण तक सीमित हूं)। अधिक हाल के भाषा मानकों का भी स्वागत है, हालांकि!


6
एक निर्माता टाइपिंग इतना भयानक ध्वनि नहीं है। जब तक आपके पास बहुत सारे सदस्य नहीं होते हैं, उस स्थिति में, शायद रिफ्लेक्टरिंग क्रम में हो।
गॉनन I

1
@Someprogrammerdude मुझे लगता है कि उनका मतलब है कि त्रुटि यह है कि आप गलती से एक प्रारंभिक मूल्य छोड़ सकते हैं
गोनेन I

2
यदि आप जानते हैं कि कैसे सरणी / वेक्टर आपको उत्तर पोस्ट करने में मदद करता है यह स्पष्ट नहीं है, मैं इसे नहीं देखता
idclev 463035818

2
@Someprogrammerdude लेकिन क्या यह एक चेतावनी भी है? इसे VS2019 के साथ नहीं देख सकते।
acraig5075

8
एक -Wmissing-field-initializersसंकलन ध्वज है।
रॉन

जवाबों:


42

यहाँ एक ट्रिक है जो एक लिंकर त्रुटि को ट्रिगर करता है यदि आवश्यक इनिशलाइज़र गायब है:

struct init_required_t {
    template <class T>
    operator T() const; // Left undefined
} static const init_required;

उपयोग:

struct Foo {
    int bar = init_required;
};

int main() {
    Foo f;
}

परिणाम:

/tmp/ccxwN7Pn.o: In function `Foo::Foo()':
prog.cc:(.text._ZN3FooC2Ev[_ZN3FooC5Ev]+0x12): undefined reference to `init_required_t::operator int<int>() const'
collect2: error: ld returned 1 exit status

चेतावनियां:

  • C ++ 14 से पहले, यह Fooसब पर एक समुच्चय होने से रोकता है।
  • यह तकनीकी रूप से अपरिभाषित व्यवहार (ODR उल्लंघन) पर निर्भर करता है, लेकिन किसी भी समझदार प्लेटफॉर्म पर काम करना चाहिए।

आप रूपांतरण ऑपरेटर को हटा सकते हैं और फिर यह एक संकलक त्रुटि है।
झटका

@ जर्क हाँ, लेकिन यह एक है जैसे ही Fooघोषित किया जाता है, भले ही आप वास्तव में ऑपरेटर को कभी नहीं कहते हैं।
क्वेंटिन

2
@ जर्क लेकिन तब यह इनिशियलाइज़ेशन प्रदान करने पर भी संकलित नहीं होता है। godbolt.org/z/yHZNq_ परिशिष्ट: MSVC के लिए यह आपके द्वारा बताए गए अनुसार काम करता है: Godbolt.org/z/uQSvDa क्या यह बग है?
n314159

बेशक, मुझे मूर्ख।
ज्रोक

6
दुर्भाग्य से, यह ट्रिक C ++ 11 के साथ काम नहीं करती है, क्योंकि यह एक गैर-समुच्चय बन जाएगा :( मैंने C ++ 11 टैग हटा दिया है, इसलिए आपका उत्तर व्यवहार्य aswell है (कृपया इसे हटाएं नहीं), लेकिन एक सी ++ 11 समाधान अभी भी पसंद किया जाता है यदि संभव हो तो,।
litb - Johannes Schaub

22

क्लैंग और जीसीसी के लिए आप यह संकलित कर सकते हैं -Werror=missing-field-initializersकि लापता फ़ील्ड इनिशियलाइज़र्स को चेतावनी को एक त्रुटि पर बदल देता है। godbolt

संपादित करें: MSVC के लिए, ऐसा लगता है कि स्तर पर भी कोई चेतावनी उत्सर्जित नहीं हुई है /Wall, इसलिए मुझे नहीं लगता कि इस संकलक के साथ लापता आरंभकर्ताओं को चेतावनी देना संभव है। godbolt


7

एक सुरुचिपूर्ण और आसान समाधान नहीं है, मुझे लगता है ... लेकिन सी ++ 11 के साथ भी काम करना चाहिए और एक संकलन-समय (लिंक-टाइम नहीं) त्रुटि देना चाहिए।

यह विचार आपकी संरचना में एक अतिरिक्त सदस्य को अंतिम स्थिति में जोड़ने का है, बिना डिफ़ॉल्ट आरंभीकरण के (और जो कि प्रकार के मूल्य के साथ आरंभ नहीं कर सकता है VariablePtr(या जो पूर्ववर्ती मानों का प्रकार है)

उदाहरण द्वारा

struct bar
 {
   bar () = delete;

   template <typename T> 
   bar (T const &) = delete;

   bar (int) 
    { }
 };

struct foo
 {
   char a;
   char b;
   char c;

   bar sentinel;
 };

इस तरह से आप अपनी समग्र आरंभीकरण सूची में सभी तत्वों को जोड़ने के लिए मजबूर हैं, जिसमें अंतिम मूल्य ( sentinelउदाहरण के लिए एक पूर्णांक ) को स्पष्ट रूप से आरंभ करने के लिए मूल्य शामिल है , या आपको "बार" त्रुटि के "हटाए गए निर्माणकर्ता को कॉल" मिलता है।

इसलिए

foo f1 {'a', 'b', 'c', 1};

संकलन और

foo f2 {'a', 'b'};  // ERROR

ऐसा नहीं करता।

दुर्भाग्य से भी

foo f3 {'a', 'b', 'c'};  // ERROR

संकलन नहीं है।

- संपादित करें -

जैसा कि MSalters (धन्यवाद) द्वारा बताया गया है, मेरे मूल उदाहरण में एक दोष (दूसरा दोष) है: एक barमूल्य को एक charमूल्य के साथ आरंभीकृत किया जा सकता है (जो कि परिवर्तनीय है int), इसलिए निम्न आरंभीकरण काम करता है

foo f4 {'a', 'b', 'c', 'd'};

और यह बहुत भ्रामक हो सकता है।

इस समस्या से बचने के लिए, मैंने निम्नलिखित हटाए गए टेम्पलेट निर्माता को जोड़ दिया है

 template <typename T> 
 bar (T const &) = delete;

इसलिए पूर्ववर्ती f4घोषणा एक संकलन त्रुटि देती है क्योंकि dमूल्य उस टेम्पलेट कंस्ट्रक्टर द्वारा अवरोधित होता है जो हटा दिया जाता है


धन्यवाद, यह अच्छा है! यह सही नहीं है जैसा कि आपने उल्लेख किया है, और foo f;संकलन करने में भी विफल रहता है, लेकिन शायद यह इस चाल के साथ एक दोष की तुलना में एक विशेषता है। स्वीकार करेंगे अगर इससे बेहतर कोई प्रस्ताव नहीं है।
जोहान्स स्काउब -

1
मैं बार कंस्ट्रक्टर को कॉन्स्टेबल नेस्टेड क्लास मेंबर के रूप में स्वीकार कर लेता हूं, जिसे पठनीयता के लिए init_list_end जैसा कुछ कहा जाता है
I

@ गोनी - पठनीयता के लिए आप एक enum, और नाम init_list_end(ओ बस list_end) का मान स्वीकार कर सकते हैं enum; लेकिन पठनीयता बहुत सारे टाइपराइटिंग जोड़ देती है, इसलिए, यह देखते हुए कि अतिरिक्त मूल्य इस उत्तर का कमजोर बिंदु है, मुझे नहीं पता कि यह एक अच्छा विचार है।
11:66 बजे अधिकतम

शायद constexpr static int eol = 0;के शीर्ष लेख में कुछ जोड़ें bartest{a, b, c, eol}मेरे लिए बहुत पठनीय लगता है।
n314159

@ n314159 - अच्छी तरह से ... बन bar::eol; यह लगभग एक enumमान के रूप में है; लेकिन मुझे नहीं लगता कि यह महत्वपूर्ण है: उत्तर का मूल "आपकी संरचना में एक अतिरिक्त सदस्य जोड़ना है, अंतिम स्थिति में, डिफ़ॉल्ट आरंभीकरण के बिना एक प्रकार का"; barहिस्सा बताते हैं कि समाधान काम करता है करने के लिए सिर्फ एक छोटी सी उदाहरण है; सटीक "डिफ़ॉल्ट प्रारंभ के बिना प्रकार" परिस्थितियों (IMHO) से निर्भर होना चाहिए।
13:66

4

के लिए CppCoreCheck वहाँ वास्तव में जाँच के लिए एक नियम है कि सभी सदस्यों से प्रारंभ कर दिया है, तो और उस चेतावनी से एक त्रुटि में बदल सकता है - निश्चित रूप से है कि आम तौर पर कार्यक्रम में व्यापक।

अपडेट करें:

आप जिस नियम को जांचना चाहते हैं, वह प्रकार का हिस्सा है Type.6:

Type.6: हमेशा एक सदस्य चर को इनिशियलाइज़ करें: हमेशा इनिशियलाइज़ करें, संभवतः डिफॉल्ट कंस्ट्रक्टर या डिफॉल्ट मेंबर इनिशियलाइज़र का उपयोग करें।


2

सबसे सरल तरीका यह है कि सदस्यों के प्रकार को नो-आर्ग कंस्ट्रक्टर नहीं दिया जाए:

struct B
{
    B(int x) {}
};
struct A
{
    B a;
    B b;
    B c;
};

int main() {

        // A a1{ 1, 2 }; // will not compile 
        A a1{ 1, 2, 3 }; // will compile 

एक अन्य विकल्प: यदि आपके सदस्य कॉन्स्टेबल हैं और आपको इन सभी को इनिशियलाइज़ करना है:

struct A {    const int& x;    const int& y;    const int& z; };

int main() {

//A a1{ 1,2 };  // will not compile 
A a2{ 1,2, 3 }; // compiles OK

यदि आप एक डमी कांस्टेबल और सदस्य के साथ रह सकते हैं, तो आप @ max66 के प्रहरी के विचार के साथ संयोजन कर सकते हैं।

struct end_of_init_list {};

struct A {
    int x;
    int y;
    int z;
    const end_of_init_list& dummy;
};

    int main() {

    //A a1{ 1,2 };  // will not compile
    //A a2{ 1,2, 3 }; // will not compile
    A a3{ 1,2, 3,end_of_init_list() }; // will compile

Cppreference https://en.cppreference.com/w/cpp/language/aggregate_initialization से

यदि आरंभिक क्लाज की संख्या सदस्यों की संख्या से कम है या आरम्भिक सूची पूरी तरह से खाली है, तो शेष सदस्य मूल्य-आरंभिक हैं। यदि एक संदर्भ प्रकार का सदस्य इन शेष सदस्यों में से एक है, तो कार्यक्रम बीमार है।

एक अन्य विकल्प अधिकतम 66 के प्रहरी विचार को लेना है और पठनीयता के लिए कुछ वाक्यगत चीनी जोड़ना है

struct init_list_guard
{
    struct ender {

    } static const end;
    init_list_guard() = delete;

    init_list_guard(ender e){ }
};

struct A
{
    char a;
    char b;
    char c;

    init_list_guard guard;
};

int main() {
   // A a1{ 1, 2 }; // will not compile 
   // A a2{ 1, init_list_guard::end }; // will not compile 
   A a3{ 1,2,3,init_list_guard::end }; // compiles OK

दुर्भाग्य से, यह Aअकल्पनीय बनाता है और कॉपी-शब्दार्थ को बदलता है ( Aअब मानों का समुच्चय नहीं है, इसलिए बोलने के लिए) :(
जोहान्स शाउब -

@ जोहान्सचैब-लिट ओके। मेरे संपादित उत्तर में इस विचार के बारे में क्या?
गोनेन I

@ जोहान्सचैब-लिटब: समान रूप से महत्वपूर्ण, पहला संस्करण सदस्यों को संकेत देकर अप्रत्यक्ष स्तर जोड़ता है। इससे भी महत्वपूर्ण बात, उन्हें किसी चीज़ का संदर्भ होना चाहिए, और 1,2,3ऑब्जेक्ट स्वचालित रूप से स्टोरेज में प्रभावी रूप से स्थानीय होते हैं जो कार्य समाप्त होने पर कार्यक्षेत्र से बाहर चले जाते हैं। और यह 64-बिट पॉइंटर्स (जैसे x86-64) वाले सिस्टम पर 3 के बजाय साइज़ोफ़ (ए) 24 बनाता है।
पीटर कॉर्डेस

एक डमी संदर्भ 3 से 16 बाइट्स तक बढ़ जाता है (सूचक (संदर्भ) सदस्य + सूचक को संरेखित करने के लिए पैडिंग।) जब तक आप संदर्भ का उपयोग कभी नहीं करते हैं, यह संभवतः ठीक है अगर यह किसी ऑब्जेक्ट से बाहर निकलता है। गुंजाइश। मैं निश्चित रूप से इसके बारे में चिंता करूँगा कि इसका अनुकूलन न हो, और इसकी नकल करना निश्चित रूप से नहीं होगा। (एक खाली वर्ग के पास अपने आकार के अलावा अन्य को अनुकूलित करने का एक बेहतर मौका है, इसलिए यहां तीसरा विकल्प सबसे कम बुरा है, लेकिन यह अभी भी कम से कम कुछ एबीआई में हर वस्तु में जगह खर्च करता है। मैं अभी भी पैडिंग के बारे में चिंता करूंगा। कुछ मामलों में अनुकूलन।)
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.