C ++ 17 ile sunulan değerlendirme sipariş garantileri nelerdir?


95

Oy kullandı etkileri nelerdir 17 değerlendirme sırası garantiler (P0145) C ++ kodunun ++ tipik C?

Aşağıdaki gibi şeyler hakkında neyi değiştirir?

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

ve

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

veya

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

C ++ 'daki atama bildiriminin sırası ile ilgili ve “C ++ Programlama Dili” 4. baskı bölüm 36.3.6'daki bu kod iyi tanımlanmış davranışa sahip mi? her ikisi de kağıt tarafından kapsanmaktadır. İlki, aşağıdaki cevabınızda güzel bir ek örnek oluşturabilir.
Shafik Yaghmour

Yanıtlar:


83

Şimdiye kadar değerlendirme sırasının belirtilmemiş olduğu bazı yaygın durumlar ile belirtilir ve geçerlidir C++17. Bazı tanımlanmamış davranışlar artık bunun yerine belirtilmemiş durumda.

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

tanımsızdı, ancak şimdi belirtilmedi. Spesifik olarak belirtilmeyen, her bir argümanın fdiğerlerine göre değerlendirildiği sıradır . i++daha önce iveya tam tersi değerlendirilebilir. Aslında, aynı derleyici altında olmasına rağmen ikinci bir çağrıyı farklı bir sırada değerlendirebilir.

Bununla birlikte, her bir argümanın değerlendirilmesi , herhangi bir başka argümanın yürütülmesinden önce tüm yan etkilerle birlikte tamamen yürütülmesi için gereklidir . Yani f(1, 1)(ikinci argüman ilk değerlendirilir) veya f(1, 2)(ilk argüman ilk değerlendirilir) alabilirsiniz. Ama asla f(2, 2)bu türden bir şey alamayacaksın .

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

belirtilmedi, ancak operatör önceliğiyle uyumlu hale gelecek ve böylece ilk değerlendirme fakışta ilk sırada olacak (aşağıdaki örnekler).

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

hala belirtilmemiş g, h ve j değerlendirme sırasına sahiptir. Söz konusu Not getf()(g(),h(),j()), kurallar devlet o getf()önce değerlendirilir g, h, j.

Ayrıca, teklif metninden aşağıdaki örneğe dikkat edin:

 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, "");

Örnek, The C ++ Programming Language , 4th edition, Stroustrup'tan geliyor ve önceden belirtilmemiş bir davranıştı, ancak C ++ 17 ile beklendiği gibi çalışacak. Devam ettirilebilir işlevlerle ( .then( . . . )) benzer sorunlar vardı .

Başka bir örnek olarak şunları düşünün:

#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 ile ve öncesinde aşağıdaki gibi sonuçlar alabiliriz (ve alacağız):

play
no,and,Work,All,

onun yerine

All,work,and,no,play

Yukarıdakilerin aslında aynı olduğuna dikkat edin

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

Ancak yine de, C ++ 17'den önce ilk aramaların akışa ilk geleceği garantisi yoktu.

Referanslar: Kabul edilen tekliften :

Sonek ifadeleri soldan sağa doğru değerlendirilir. Bu, işlev çağrılarını ve üye seçim ifadelerini içerir.

Atama ifadeleri sağdan sola doğru değerlendirilir. Bu, bileşik atamaları içerir.

Operatörleri kaydırmak için işlenenler soldan sağa doğru değerlendirilir. Özetle, aşağıdaki ifadeler a, sonra b, sonra c, sonra d sırasıyla değerlendirilir:

  1. ab
  2. a-> b
  3. a -> * b
  4. a (b1, b2, b3)
  5. b @ = a
  6. a [b]
  7. a << b
  8. a >> b

Ayrıca, aşağıdaki ek kuralı öneriyoruz: aşırı yüklenmiş bir operatörü içeren bir ifadenin değerlendirme sırası, işlev çağrıları için kurallar değil, karşılık gelen yerleşik operatörle ilişkili sırayla belirlenir.

Notu düzenle: Orijinal cevabım yanlış yorumlandı a(b1, b2, b3). Sırası b1, b2, b3hala belirsizdir. (teşekkür ederim @KABoissonneault, tüm yorumcular.)

Ancak, (@Yakk işaret ettiği gibi) ve bu önemli: dahi b1, b2, b3, önemsiz olmayan ifadelerdir her biri tamamen değerlendirilir ve ilgili fonksiyon parametreye bağlı Diğerleri değerlendirilmeye başlanmıştır önce. Standart bunu şu şekilde ifade eder:

§5.2.2 - İşlev çağrısı 5.2.2.4:

. . . Sonek ifadesi, ifade listesindeki her ifadeden ve herhangi bir varsayılan bağımsız değişkenden önce sıralanır. Bir parametrenin başlatılmasıyla ilişkili her değer hesaplaması ve yan etki ve başlatmanın kendisi, her değer hesaplamasından ve sonraki herhangi bir parametrenin başlatılmasıyla ilişkili yan etkiden önce sıralanır.

Ancak, GitHub taslağında şu yeni cümlelerden biri eksik :

Bir parametrenin başlatılmasıyla ilişkili her değer hesaplaması ve yan etki ve başlatmanın kendisi, her değer hesaplamasından ve sonraki herhangi bir parametrenin başlatılmasıyla ilişkili yan etkiden önce sıralanır.

Örnek olduğunu var. Onlarca yıllık sorunları ( Herb Sutter tarafından açıklandığı gibi ) istisnai güvenlik ile çözer.

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

f(get_raw_a(), get_raw_a());

çağrılardan biri get_raw_a(), diğer ham işaretçi akıllı işaretçi parametresine bağlanmadan önce atarsa ​​sızıntı olur .

TC'nin belirttiği gibi, ham göstericiden benzersiz_ptr yapısı açık olduğundan, bunun derlenmesini engellediğinden örnek kusurludur. *

Ayrıca, bu klasik not soruyu (etiketli C , değil C ++ ):

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

hala tanımsız.


1
"İkinci bir yardımcı teklif, aşağıdaki gibi işlev çağrılarının değerlendirme sırasının yerini alır: işlev tüm bağımsız değişkenlerinden önce değerlendirilir, ancak herhangi bir bağımsız değişken çifti (bağımsız değişken listesinden) belirsiz bir şekilde sıralanır; yani biri diğerinden önce değerlendirilir, ancak sipariş belirtilmez; fonksiyonun argümanlardan önce değerlendirilmesi garanti edilir. Bu, Çekirdek Çalışma Grubunun bazı üyeleri tarafından yapılan bir öneriyi yansıtır. "
Yakk - Adam Nevraumont

1
Kağıttan şu izlenimi alıyorum: "Aşağıdaki ifadeler sırayla değerlendirilir a, sonra b, sonra csonra değerlendirilir d" ve sonra göstererek a(b1, b2, b3), tüm bifadelerin mutlaka herhangi bir sırayla değerlendirilmeyeceğini öne sürüyorum (aksi takdirde, olurdu a(b, c, d))
KABoissonneault

1
@KABoissoneault, haklısınız ve cevabı buna göre güncelledim. Ayrıca, hepsi: alıntılar , anladığım kadarıyla versiyonda oylanan form versiyon 3'tür.
Johan Lundberg

2
@JohanLundberg Gazeteden önemli olduğuna inandığım bir şey daha var. a(b1()(), b2()())sipariş edebilirsiniz b1()()ve b2()()herhangi bir sırada, ancak edemez yapmak b1()sonra b2()()o b1()(): o artık infazlar boş yaprak may. Kısaca, "8. İŞLEVLİ ARAMALAR İÇİN ALTERNATİF DEĞERLENDİRME SİPARİŞİ" onaylanan değişikliğin bir parçasıydı.
Yakk - Adam Nevraumont

3
f(i++, i)tanımsızdı. Şimdi belirtilmedi. Stroustrup'ın string örneği muhtemelen belirsizdi, tanımsız değildi. `f (get_raw_a (), get_raw_a ());` ilgili unique_ptrkurucu açık olduğundan derlenmez . Son olarak, x++ + ++xtanımsız, nokta.
TC

44

C ++ 17'de serpiştirme yasaktır

C ++ 14'te aşağıdakiler güvenli değildi:

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

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

İşlev çağrısı sırasında burada gerçekleşen dört işlem vardır

  1. new A
  2. unique_ptr<A> kurucu
  3. new B
  4. unique_ptr<B> kurucu

Bunların sıralaması tamamen belirsizdi ve bu nedenle mükemmel bir şekilde geçerli bir sıralama (1), (3), (2), (4). Bu sıralama seçildiyse ve (3) atarsa, (1) 'den gelen bellek sızıntı yapar - (2)' yi henüz çalıştırmadık, bu da sızıntıyı önlerdi.


C ++ 17'de, yeni kurallar serpiştirmeyi yasaklar. [İntro.execution] 'dan:

Her işlev F çağrısı için, F içinde gerçekleşen her A değerlendirmesi ve F içinde gerçekleşmeyen ancak aynı iş parçacığı üzerinde ve aynı sinyal işleyicinin bir parçası olarak (varsa) değerlendirilen her değerlendirme B için, ya A B'den önce sıralanır veya B, A'dan önce sıralanır.

Bu cümlenin bir dipnotu var:

Başka bir deyişle, işlev yürütmeleri birbiri içine girmez.

Bu bize iki geçerli sıralama bırakır: (1), (2), (3), (4) veya (3), (4), (1), (2). Hangi siparişin alındığı belirtilmemiştir, ancak her ikisi de güvenlidir. (1) (3) 'ün her ikisinin de (2) ve (4)' ten önce olduğu tüm sıralamalar artık yasaklanmıştır.


1
Biraz bir kenara, ancak bu, boost :: make_shared ve daha sonra std :: make_shared'in nedenlerinden biriydi (diğer neden daha az tahsis + daha iyi yerelliktir). Görünüşe göre istisnai güvenlik / kaynak sızıntısı motivasyonu artık geçerli değil. Bkz. Kod Örneği 3, boost.org/doc/libs/1_67_0/libs/smart_ptr/doc/html/… Düzenle ve stackoverflow.com/a/48844115 , herbutter.com/2013/05/29/gotw-89-solution- smart-pointers
Max Barraclough

3
Bu değişikliğin optimizasyonu nasıl etkilediğini merak ediyorum. Derleyici, argüman hesaplamasıyla ilgili CPU talimatlarının nasıl birleştirileceği ve harmanlanacağı konusunda seçenek sayısını ciddi ölçüde azaltmıştır, bu nedenle daha zayıf CPU kullanımına yol açabilir mi?
Violet Zürafa

2

İfade değerlendirme sırası hakkında bazı notlar buldum:

  • Hızlı S: c ++, işlev bağımsız değişkenlerini değerlendirmek için neden belirli bir sıraya sahip değil?

    Bazı değerlendirme sıralaması, C ++ 17'de eklendiğinde aşırı yüklenmiş operatörleri ve tam bağımsız değişken kurallarını garanti eder. Ancak, hangi argümanın önce geleceği belirsiz bırakılır. C ++ 17'de, aranacak şeyi veren ifadenin ((işlev çağrısının) solundaki kodun argümanlardan önce geldiği ve hangisi önce değerlendirilirse değerlendirilsin, bir sonrakinden önce tam olarak değerlendirildiği belirtilmiştir. başlatılır ve bir nesne yöntemi söz konusu olduğunda, nesnenin değeri yönteme ilişkin argümanlar yapılmadan önce değerlendirilir.

  • Değerlendirme sırası

    21) Parantezli bir başlatıcıdaki virgülle ayrılmış ifadeler listesindeki her ifade, bir işlev çağrısı gibi değerlendirilir ( belirsiz olarak sıralı )

  • Belirsiz ifadeler

    C ++ dili, bir işlev çağrısına yönelik bağımsız değişkenlerin değerlendirilme sırasını garanti etmez.

In Deyimsel C için ifade değerlendirme Order P0145R3.Refining ++ buldum:

Sonek ifadesinin değer hesaplaması ve ilişkili yan etkisi, ifade listesindeki ifadelerden önce sıralanır. Bildirilen parametrelerin ilklendirmeleri, araya ekleme olmaksızın belirsiz bir şekilde sıralanır .

Ancak bunu standart olarak bulamadım, bunun yerine standart olarak buldum:

6.8.1.8 Sıralı yürütme [giriş.yürütme] Her değer hesaplaması ve X ifadesiyle ilişkili her yan etki, her değer hesaplamasından ve Y ifadesiyle ilişkili her yan etkiden önce sıralanırsa, X ifadesinin Y ifadesinden önce sıralandığı söylenir .

6.8.1.9 Sıralı yürütme [giriş.yürütme] Bir tam ifade ile ilişkili her değer hesaplaması ve yan etki, değerlendirilecek bir sonraki tam ifade ile ilişkili her değer hesaplamasından ve yan etkiden önce sıralanır.

7.6.19.1 Virgül operatörü [ifade.com] Virgülle ayrılmış bir çift ifade soldan sağa değerlendirilir; ...

Böylece, 14 ve 17 standart için üç derleyicideki davranışları karşılaştırdım. Keşfedilen kod:

#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());
}

Sonuçlar (daha tutarlı olan clang'dır):

<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>

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.