C ++ 17 द्वारा प्रस्तुत मूल्यांकन आदेश गारंटी क्या हैं?


95

C ++ 17 मूल्यांकन आदेश की गारंटी (P0145) में विशिष्ट C ++ कोड पर मतदान के निहितार्थ क्या हैं ?

यह निम्नलिखित चीजों की तरह क्या बदलता है?

i = 1;
f(i++, i)

तथा

std::cout << f() << f() << f();

या

f(g(), h(), j());

सी ++ में असाइनमेंट स्टेटमेंट के मूल्यांकन के आदेश से संबंधित है और क्या "सी ++ प्रोग्रामिंग लैंग्वेज" से यह कोड 4 वें संस्करण खंड 36.3.6 में अच्छी तरह से परिभाषित व्यवहार है? जो दोनों कागज द्वारा कवर किए गए हैं। पहला व्यक्ति आपके उत्तर में एक अच्छा अतिरिक्त उदाहरण दे सकता है।
शफीक याघमौर

जवाबों:


83

कुछ सामान्य मामले जहां मूल्यांकन आदेश अभी तक अनिर्दिष्ट है , के साथ निर्दिष्ट और मान्य हैं C++17। कुछ अपरिभाषित व्यवहार अब अनिर्दिष्ट है।

i = 1;
f(i++, i)

अपरिभाषित था, लेकिन अब अनिर्दिष्ट है। विशेष रूप से, जो निर्दिष्ट नहीं है वह वह क्रम है जिसमें प्रत्येक तर्क fका दूसरों के सापेक्ष मूल्यांकन किया जाता है। i++पहले iया इसके विपरीत मूल्यांकन किया जा सकता है । वास्तव में, यह एक ही संकलक के अधीन होने के बावजूद एक अलग क्रम में दूसरी कॉल का मूल्यांकन कर सकता है।

हालांकि, प्रत्येक तर्क के मूल्यांकन को किसी भी अन्य तर्क के निष्पादन से पहले, सभी दुष्प्रभावों के साथ, पूरी तरह से निष्पादित करने की आवश्यकता होती है। तो आपको मिल सकता है f(1, 1)(दूसरे तर्क का मूल्यांकन पहले) या f(1, 2)(पहले तर्क का मूल्यांकन पहले)। लेकिन आपको कभी f(2, 2)भी या उस प्रकृति का कुछ भी नहीं मिलेगा ।

std::cout << f() << f() << f();

अनिर्दिष्ट था, लेकिन यह ऑपरेटर की पूर्वता के साथ संगत हो जाएगा ताकि fधारा का पहला मूल्यांकन पहली बार (नीचे उदाहरण में) आएगा।

f(g(), h(), j());

अभी भी जी, एच, और जे के अनिर्दिष्ट मूल्यांकन आदेश है। ध्यान दें कि getf()(g(),h(),j()), नियमों का getf()मूल्यांकन पहले किया जाएगा g, h, j

प्रस्ताव पाठ से निम्नलिखित उदाहरण पर भी ध्यान दें:

 std::string s = "but I have heard it works even if you don't believe in it"
 s.replace(0, 4, "").replace(s.find("even"), 4, "only")
  .replace(s.find(" don't"), 6, "");

इसका उदाहरण द C ++ प्रोग्रामिंग लैंग्वेज , 4th एडिशन, स्ट्रॉस्ट्रुप, और अनिर्दिष्ट व्यवहार के रूप में आता है, लेकिन C ++ 17 के साथ यह उम्मीद के मुताबिक काम करेगा। पुन: प्रयोज्य कार्यों ( .then( . . . )) के साथ इसी तरह के मुद्दे थे ।

एक अन्य उदाहरण के रूप में, निम्नलिखित पर विचार करें:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

struct Speaker{
    int i =0;
    Speaker(std::vector<std::string> words) :words(words) {}
    std::vector<std::string> words;
    std::string operator()(){
        assert(words.size()>0);
        if(i==words.size()) i=0;
        // Pre-C++17 version:
        auto word = words[i] + (i+1==words.size()?"\n":",");
        ++i;
        return word;
        // Still not possible with C++17:
        // return words[i++] + (i==words.size()?"\n":",");

    }
};

int main() {
    auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
    std::cout << spk() << spk() << spk() << spk() << spk() ;
}

C ++ 14 के साथ और इससे पहले कि हम (और करेंगे) इस तरह के परिणाम प्राप्त करेंगे

play
no,and,Work,All,

के बजाय

All,work,and,no,play

ध्यान दें कि ऊपर जैसा प्रभाव है

(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;

लेकिन फिर भी, C ++ 17 से पहले कोई गारंटी नहीं थी कि पहली कॉल स्ट्रीम में पहले आएगी।

संदर्भ: स्वीकृत प्रस्ताव से :

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

असाइनमेंट अभिव्यक्तियों का मूल्यांकन दाईं से बाईं ओर किया जाता है। इसमें कंपाउंड असाइनमेंट शामिल हैं।

ऑपरेटर्स को शिफ्ट करने वाले ऑपरेटरों का मूल्यांकन बाएं से दाएं किया जाता है। सारांश में, निम्नलिखित अभिव्यक्तियों का मूल्यांकन क्रम में किया जाता है a, फिर b, c, फिर d:

  1. अब
  2. a-> ख
  3. एक -> * ख
  4. ए (बी १, बी २, बी ३)
  5. b @ = a
  6. एक [ख]
  7. a << b
  8. एक >> बी

इसके अलावा, हम निम्नलिखित अतिरिक्त नियम का सुझाव देते हैं: एक अधिभार ऑपरेटर को शामिल करने वाली अभिव्यक्ति के मूल्यांकन का क्रम संबंधित बिल्ट-इन ऑपरेटर से जुड़े आदेश से निर्धारित होता है, न कि फ़ंक्शन कॉल के लिए नियम।

नोट संपादित करें: मेरा मूल उत्तर गलत व्याख्या किया गया है a(b1, b2, b3)। के आदेश b1, b2, b3अभी भी अनिर्दिष्ट है। (साभार @KABoissonneault, सभी टिप्पणीकार।)

हालांकि, (@Yakk बताते हैं) और यह महत्वपूर्ण है: यहां तक कि जब b1, b2, b3, गैर तुच्छ अभिव्यक्ति कर रहे हैं उनमें से प्रत्येक पूरी तरह से मूल्यांकन किया जाता है और संबंधित समारोह पैरामीटर से बंधा अन्य लोगों का मूल्यांकन किया जाना शुरू कर रहे हैं इससे पहले कि। मानक इस प्रकार बताता है:

§5.2.2 - फंक्शन कॉल 5.2.2.4:

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

हालाँकि, इन नए वाक्यों में से एक GitHub ड्राफ्ट से गायब है :

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

इसका उदाहरण है । यह अपवाद की सुरक्षा के साथ दशकों पुरानी समस्याओं ( हर्ब सटर द्वारा बताई गई ) को हल करता है जहां चीजें पसंद हैं

f(std::unique_ptr<A> a, std::unique_ptr<B> b);

f(get_raw_a(), get_raw_a());

अगर कोई कॉल get_raw_a()दूसरे कच्चे पॉइंटर को उसके स्मार्ट पॉइंटर पैरामीटर से बांधने से पहले फेंक देगा तो लीक हो जाएगी।

जैसा कि टीसी द्वारा बताया गया है, उदाहरण के लिए त्रुटिपूर्ण है क्योंकि यूनिक_प्ट्र कच्चे संकेतक से निर्माण स्पष्ट है, जिससे इसे संकलित होने से रोका जा सके। *

इस शास्त्रीय प्रश्न पर भी ध्यान दें ( C को टैग नहीं, C ++ ):

int x=0;
x++ + ++x;

अभी भी अपरिभाषित है।


1
"एक दूसरा, सहायक प्रस्ताव फ़ंक्शन कॉल के मूल्यांकन क्रम को निम्नानुसार बदलता है: फ़ंक्शन का मूल्यांकन उसके सभी तर्कों से पहले किया जाता है, लेकिन किसी भी तर्क (तर्क सूची से) की जोड़ी अनिश्चित रूप से अनुक्रमित होती है; जिसका अर्थ है कि एक का मूल्यांकन दूसरे से पहले किया जाता है लेकिन आदेश निर्दिष्ट नहीं है; यह गारंटी है कि तर्कों से पहले फ़ंक्शन का मूल्यांकन किया जाता है। यह कोर वर्किंग ग्रुप के कुछ सदस्यों द्वारा किए गए सुझाव को दर्शाता है। "
यक्क - एडम नेवरामोंट

1
मुझे यह कहते हुए पेपर से आभास हो रहा है कि "निम्न अभिव्यक्तियों का मूल्यांकन क्रम में किया जाता है a, फिर b, फिर c, d" और फिर दिखाते हुए a(b1, b2, b3), कि सभी bअभिव्यक्तियों का मूल्यांकन किसी भी क्रम में आवश्यक नहीं है (अन्यथा, यह होगा a(b, c, d))
KABoissonneault

1
@KABoissoneault, आप सही हैं और मैंने तदनुसार उत्तर अपडेट किया है। इसके अलावा, सभी: उद्धरण हैं प्रपत्र 3 संस्करण है, जो जहाँ तक मैं समझता हूँ संस्करण में मतदान किया है।
जोहान लुंडबर्ग

2
@JohanLundberg कागज से एक और बात है जो मुझे विश्वास है कि महत्वपूर्ण है। a(b1()(), b2()())आदेश कर सकते हैं b1()()और b2()()किसी भी क्रम में है, लेकिन यह नहीं कर सकते करना b1()तो b2()()फिर b1()(): यह उनकी फांसी Interleave नहीं रह गया है हो सकता है। संक्षेप में, "8. समारोह के लिए वैकल्पिक मूल्यांकन आदेश" अनुमोदित परिवर्तन का हिस्सा था।
यक्क - एडम नेवरामोंट

3
f(i++, i)अपरिभाषित था। यह अब अनिर्दिष्ट है। स्ट्रॉस्ट्रुप का स्ट्रिंग उदाहरण शायद अनिर्दिष्ट था, अपरिभाषित नहीं। `एफ (get_raw_a (), get_raw_a ());` संबंधित unique_ptrकंस्ट्रक्टर के स्पष्ट होने के बाद संकलन नहीं करेगा । अंत में, x++ + ++xअपरिभाषित है, अवधि।
TC

44

इंटरलीविंग C ++ 17 में निषिद्ध है

C ++ 14 में, निम्नलिखित असुरक्षित था:

void foo(std::unique_ptr<A>, std::unique_ptr<B>);

foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));

फ़ंक्शन कॉल के दौरान यहां चार ऑपरेशन होते हैं

  1. new A
  2. unique_ptr<A> निर्माता
  3. new B
  4. unique_ptr<B> निर्माता

इनका आदेश देना पूरी तरह से अनिर्दिष्ट था, और इसलिए पूरी तरह से मान्य आदेश (1), (3), (2), (4) है। यदि इस आदेश का चयन किया गया और (3) फेंकता है, तो मेमोरी (1) लीक से - हम अभी तक (2) नहीं चले हैं, जिससे रिसाव को रोका जा सकेगा।


C ++ 17 में, नए नियम इंटरलेविंग को प्रतिबंधित करते हैं। [Intro.execution] से:

प्रत्येक फ़ंक्शन के लिए एफ, हर मूल्यांकन ए के लिए, जो एफ के भीतर होता है और प्रत्येक मूल्यांकन बी के भीतर होता है जो एफ के भीतर नहीं होता है, लेकिन एक ही थ्रेड पर और उसी सिग्नल हैंडलर (यदि कोई हो) के हिस्से के रूप में मूल्यांकन किया जाता है, तो ए को बी से पहले अनुक्रमित किया जाता है। या B, A से पहले अनुक्रमित है।

उस वाक्य का एक फुटनोट है जिसमें लिखा है:

दूसरे शब्दों में, फ़ंक्शन निष्पादन एक-दूसरे के साथ हस्तक्षेप नहीं करते हैं।

यह हमें दो मान्य आदेशों के साथ छोड़ देता है: (1), (2), (3), (4) या (3), (4), (1), (2)। यह अनिर्दिष्ट है कि कौन सा ऑर्डर लिया जाता है, लेकिन ये दोनों सुरक्षित हैं। सभी आदेश जहां (1) (3) दोनों पहले (2) और (4) पहले से प्रतिबंधित हैं।


1
एक तरफ थोड़ा सा, लेकिन यह बढ़ावा देने के कारणों में से एक था :: make_sared, और बाद में std :: make_sared (अन्य कारण कम आवंटन + बेहतर इलाके)। अपवाद-सुरक्षा / संसाधन-रिसाव प्रेरणा जैसी आवाज़ अब लागू नहीं होती है। कोड देखें उदाहरण 3, boost.org/doc/libs/1_67_0/libs/smart_ptr/doc/html/… [ संपादित करें और stackoverflow.com/a/48844115 , जड़ी बूटियों का कारोबार.कॉम 2013
मैक्स बैरक्लोफ

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

2

मुझे अभिव्यक्ति मूल्यांकन क्रम के बारे में कुछ नोट्स मिले हैं:

  • क्विक क्यू: फ़ंक्शन तर्कों के मूल्यांकन के लिए c ++ के पास निर्दिष्ट आदेश क्यों नहीं है?

    मूल्यांकन के कुछ आदेश ओवरलोड ऑपरेटरों और पूर्ण-तर्क नियमों के आसपास की गारंटी देते हैं, जहां C ++ 17 में जोड़ा गया है। लेकिन यह बना हुआ है कि कौन सा तर्क पहले चला जाता है अनिर्दिष्ट। C ++ 17 में, अब यह निर्दिष्ट किया गया है कि कॉल करने के लिए (फ़ंक्शन फ़ंक्शन के बाईं ओर कोड) कॉल करने के लिए जो अभिव्यक्ति दे रहा है, वह तर्क से पहले जाता है, और जो भी तर्क का मूल्यांकन किया जाता है, उसका मूल्यांकन अगले एक से पहले पूरी तरह से किया जाता है। शुरू किया, और एक वस्तु विधि के मामले में विधि के मूल्य का मूल्यांकन विधि के तर्कों से पहले किया जाता है।

  • मूल्यांकन का आदेश

    21) अल्पविराम शुरुआती में अभिव्यक्ति की अल्पविराम से अलग सूची में हर अभिव्यक्ति का मूल्यांकन किया जाता है जैसे कि एक फ़ंक्शन कॉल के लिए ( अनिश्चित काल के क्रम में )

  • अस्पष्ट भाव

    C ++ भाषा उस आदेश की गारंटी नहीं देती है जिसमें फ़ंक्शन कॉल के तर्क का मूल्यांकन किया जाता है।

में मुहावरेदार सी के लिए अभिव्यक्ति मूल्यांकन आदेश P0145R3.Refining ++ मैंने पाया:

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

लेकिन मुझे यह मानक में नहीं मिला, इसके बजाय मानक में मैंने पाया है:

6.8.1.8 अनुक्रमिक निष्पादन [intro.execution] एक एक्सप्रेशन X को एक एक्सप्रेशन Y से पहले अनुक्रमित करने के लिए कहा जाता है, यदि एक्सप्रेशन और हर साइड इम्पैक्ट एक्सप्रेशन से जुड़े हर साइड इम्पैक्ट से पहले सिक्वेंस किया जाता है और एक्सप्रेशन Y के साथ हर साइड इफेक्ट होता है। ।

6.8.1.9 अनुक्रमिक निष्पादन [intro.execution] एक पूर्ण-अभिव्यक्ति के साथ जुड़े प्रत्येक मूल्य संगणना और साइड इफेक्ट का मूल्यांकन किया जाने वाले अगले पूर्ण-अभिव्यक्ति के साथ जुड़े प्रत्येक मूल्य संगणना और साइड इफेक्ट से पहले किया जाता है।

7.6.19.1 कोमा संचालक [expr.comma] एक अल्पविराम द्वारा अलग किए गए भावों की एक जोड़ी को बाएं से दाएं मूल्यांकन किया जाता है ...;

इसलिए, मैंने 14 और 17 मानकों के लिए तीन संकलक के व्यवहार की तुलना की। खोजा गया कोड है:

#include <iostream>

struct A
{
    A& addInt(int i)
    {
        std::cout << "add int: " << i << "\n";
        return *this;
    }

    A& addFloat(float i)
    {
        std::cout << "add float: " << i << "\n";
        return *this;
    }
};

int computeInt()
{
    std::cout << "compute int\n";
    return 0;
}

float computeFloat()
{
    std::cout << "compute float\n";
    return 1.0f;
}

void compute(float, int)
{
    std::cout << "compute\n";
}

int main()
{
    A a;
    a.addFloat(computeFloat()).addInt(computeInt());
    std::cout << "Function call:\n";
    compute(computeFloat(), computeInt());
}

परिणाम (अधिक सुसंगत है)

<style type="text/css">
  .tg {
    border-collapse: collapse;
    border-spacing: 0;
    border-color: #aaa;
  }
  
  .tg td {
    font-family: Arial, sans-serif;
    font-size: 14px;
    padding: 10px 5px;
    border-style: solid;
    border-width: 1px;
    overflow: hidden;
    word-break: normal;
    border-color: #aaa;
    color: #333;
    background-color: #fff;
  }
  
  .tg th {
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: normal;
    padding: 10px 5px;
    border-style: solid;
    border-width: 1px;
    overflow: hidden;
    word-break: normal;
    border-color: #aaa;
    color: #fff;
    background-color: #f38630;
  }
  
  .tg .tg-0pky {
    border-color: inherit;
    text-align: left;
    vertical-align: top
  }
  
  .tg .tg-fymr {
    font-weight: bold;
    border-color: inherit;
    text-align: left;
    vertical-align: top
  }
</style>
<table class="tg">
  <tr>
    <th class="tg-0pky"></th>
    <th class="tg-fymr">C++14</th>
    <th class="tg-fymr">C++17</th>
  </tr>
  <tr>
    <td class="tg-fymr"><br>gcc 9.0.1<br></td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
  </tr>
  <tr>
    <td class="tg-fymr">clang 9</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
  </tr>
  <tr>
    <td class="tg-fymr">msvs 2017</td>
    <td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
  </tr>
</table>

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