Yöntem zincirinde C ++ yürütme sırası


108

Bu programın çıktısı:

#include <iostream> 
class c1
{   
  public:
    c1& meth1(int* ar) {
      std::cout << "method 1" << std::endl;
      *ar = 1;
      return *this;
    }
    void meth2(int ar)
    {
      std::cout << "method 2:"<< ar << std::endl;
    }
};

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu).meth2(nu);
}

Dır-dir:

method 1
method 2:0

Başladığında neden nu1 değil meth2()?


42
@MartinBonner: Cevabı bilmeme rağmen , kelimenin hiçbir anlamıyla "bariz" demezdim ve öyle olsa bile, bu olumsuz oylama için uygun bir neden olmazdı. Hayal kırıklığı!
Orbit'te Hafiflik Yarışları

4
Argümanlarınızı değiştirdiğinizde elde ettiğiniz şey budur. Argümanlarını değiştiren işlevlerin okunması daha zordur, etkileri bir sonraki programcının kod üzerinde çalışması için beklenmediktir ve bunun gibi sürprizlere yol açar. Çağrı dışında herhangi bir parametreyi değiştirmekten kaçınmanızı şiddetle öneririm. Çağırıcıyı değiştirmek burada sorun olmaz, çünkü ikinci yöntem ilkinin sonucuna göre çağrılır, bu nedenle etkiler üzerinde sıralanır. Yine de olmayacakları bazı durumlar var.
Jan Hudec


@JanHudec Fonksiyonel programlamanın fonksiyon saflığına bu kadar büyük önem vermesinin nedeni tam da budur.
Pharap

2
Bir örnek olarak, bir yığın tabanlı çağırarak kongre muhtemelen itme tercih ettiğini nu, &nuve csonra çağırmak, bu sırayla yığınına üzerine meth1daha sonra çağırmak, yığının üzerine sonucunu itin meth2bir kayıt tabanlı çağıran kongre isteyeyim iken, yük cve &nuiçine kayıtları, çağırmak meth1, yük nubir kayıt içine, daha sonra çağırmak meth2.
Neil

Yanıtlar:


66

Çünkü değerlendirme sırası belirtilmemiş.

Sen görüyoruz nuiçinde maindeğerlendirilmekte olup 0bile öncemeth1 denir. Zincirlemeyle ilgili sorun budur. Bunu yapmamayı tavsiye ederim.

Güzel, basit, anlaşılır, okunması kolay, anlaşılması kolay bir program hazırlayın:

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu);
  c.meth2(nu);
}

14
C ++ 17
Revolver_Ocelot

7
Yöntem zincirlemeyi seviyorum (örn <<. Çıktı için ve oluşturuculara çok fazla argüman içeren karmaşık nesneler için "nesne oluşturucular" için - ama çıktı argümanlarıyla gerçekten kötü bir şekilde karışıyor.
Martin Bonner Monica'yı

34
Bunu doğru anlıyor muyum? değerlendirilmesi sırası meth1ve meth2tanımlanmış, ancak kullanılacak parametrenin değerlendirilmesi meth2önce ortaya çıkabilir meth1denir ...?
Roddy

7
Yöntem zincirleme, yöntemler mantıklı olduğu ve yalnızca invocantı değiştirdiği sürece iyidir (bunun için etkiler iyi sıralanmıştır, çünkü ikinci yöntem birincinin sonucuna göre çağrılır).
Jan Hudec

4
Düşündüğünüz zaman mantıklı. Gibi çalışırmeth2(meth1(c, &nu), nu)
BartekChom

29

Taslak standardın değerlendirme sırasına ilişkin bu kısmının konuyla ilgili olduğunu düşünüyorum:

1.9 Program Yürütme

...

  1. Belirtilenler dışında, tek tek operatörlerin işlenenlerinin ve tek tek ifadelerin alt ifadelerinin değerlendirmeleri sıralı değildir. Bir operatörün işlenenlerinin değer hesaplamaları, operatörün sonucunun değer hesaplamasından önce sıralanır. Bir skaler nesne üzerindeki bir yan etki, aynı skaler nesne üzerindeki başka bir yan etkiye veya aynı skaler nesnenin değerini kullanan bir değer hesaplamasına göre sıralanmamışsa ve potansiyel olarak eşzamanlı değilse, davranış tanımsızdır.

ve ayrıca:

5.2.2 İşlev çağrısı

...

  1. [Not: Sonek ifadesinin ve bağımsız değişkenlerin değerlendirmeleri birbirine göre sıralanmamıştır. Bağımsız değişken değerlendirmelerinin tüm yan etkileri, işleve girilmeden önce sıralanır - son not]

Öyleyse, satırınız c.meth1(&nu).meth2(nu);için, son çağrı için fonksiyon çağrısı operatörü açısından operatörde neler olduğunu düşünün meth2, böylece postfix ifadesi ve argümanındaki dökümü açıkça görüyoruz nu:

operator()(c.meth1(&nu).meth2, nu);

Sonek ekspresyonu ve argüman değerlendirmeler son işlev çağrısı için (sonek ifade yani c.meth1(&nu).meth2ve nu) olan birbirine unsequenced göre uygun olarak işlev çağrısı üzerinde kural. Bu nedenle, skaler nesne üzerindeki sonek ifadesinin hesaplanmasının yan etkisi , işlev çağrısından önceki arargüman değerlendirmesine göre sıralanmaz . Tarafındannumeth2 program yürütme yukarıdaki kural, bu tanımsız davranıştır.

Diğer bir deyişle, derleyicinin çağrıdan sonra çağrının nuargümanı değerlendirmesine gerek yoktur - etkinin hiçbir yan etkisi olmadığını varsaymak ücretsizdir .meth2meth1meth1nu değerlendirmeyi .

Yukarıda üretilen montaj kodu, mainfonksiyonda aşağıdaki sırayı içerir :

  1. Değişken nu yığın üzerinde tahsis edilir ve 0 ile başlatılır.
  2. Bir kayıt (ebx benim durumumda) değerinin bir kopyasını alırnu
  3. Adresleri nuvec parametre kayıtlarına yüklenir
  4. meth1 denir
  5. Dönüş değeri kayıt ve önceden önbelleğe değer arasında nuyerebx kayıt parametre kaydediciler tarafından ön değerlere
  6. meth2 denir

Kritik olarak, yukarıdaki 5. adımda derleyici, nu2. adımdaki önbelleğe alınan değerin işlev çağrısında yeniden kullanılmasına izin verir meth2. Burada nu, çağrı ile değiştirilmiş olabilecek olasılığı göz ardı eder .meth1 , eylemde 'tanımsız davranış' .

NOT: Bu cevabın özü orijinal halinden farklıdır. Son işlev çağrısından önce sıralanmamış olan işlenen hesaplamasının yan etkileri açısından ilk açıklamam yanlıştı çünkü yanlıştı. Sorun, işlenenlerin kendilerinin hesaplanmasının belirsiz bir şekilde sıralanması gerçeğidir.


2
Bu yanlış. İşlev çağrıları, çağıran işlevdeki diğer değerlendirmelerle belirsiz bir şekilde sıralanır (önceden dizilenmiş bir sınırlama başka türlü uygulanmadıkça); araya girmezler.
TC

1
@TC - Araya eklenen işlev çağrıları hakkında hiçbir şey söylemedim. Sadece operatörlerin yan etkilerinden bahsetmiştim. Yukarıdakiler tarafından üretilen derleme koduna bakarsanız, bunun meth1daha önce çalıştırıldığını görürsünüz meth2, ancak için parametresi , çağrılmadan önce bir kayıtta önbelleğe alınmış meth2bir değerdir - yani derleyici, olası yan etkileri görmezden geldi, cevabımla tutarlı. numeth1
Smeeheey

1
Tam olarak - "yan etkisinin (yani ar değerini ayarlamak), çağrıdan önce sıralanmasının garanti edilmediğini" iddia ediyorsunuz. Bir işlev çağrısındaki sonek ifadesinin değerlendirilmesi (yani c.meth1(&nu).meth2) ve bu çağrıya ( nu) yönelik argümanın değerlendirilmesi genellikle sıralanmaz, ancak 1) yan etkilerinin tümü girişten önce sıralanır meth2ve 2) çünkü c.meth1(&nu)bir işlev çağrısıdır , değerlendirilmesi ile belirsiz bir şekilde sıralanır nu. İçeride meth2, eğer bir şekilde içindeki değişkene bir işaretçi elde mainederse, her zaman 1'i görürdü.
TC

2
"Bununla birlikte, işlenenlerin hesaplanmasının yan etkisinin (yani ar değerinin ayarlanması), yukarıdaki herhangi bir şeyden önce (2'ye göre) sıralanması garanti edilmez." meth2Alıntı yaptığınız cppreference sayfasının 3. maddesinde belirtildiği gibi (ayrıca uygun şekilde alıntı yapmayı da ihmal ettiniz) , çağrılmadan önce sıralanması kesinlikle garanti edilir .
TC

1
Bir şeyi yanlış anladın ve daha da kötüleştirdin. Burada kesinlikle tanımlanmamış bir davranış yok. Örneği geçen [intro.execution] / 15 okumaya devam edin.
TC

9

1998 C ++ standardında, Bölüm 5, para 4

Belirtilenler dışında, tek tek operatörlerin işlenenlerinin ve tek tek ifadelerin alt ifadelerinin değerlendirme sırası ve yan etkilerin meydana gelme sırası belirtilmemiştir. Önceki ve sonraki sıra noktası arasında, bir skaler nesnenin saklanan değeri, bir ifadenin değerlendirilmesiyle en fazla bir kez değiştirilecektir. Ayrıca, önceki değere yalnızca depolanacak değeri belirlemek için erişilecektir. Bu paragrafın gereklilikleri, bir tam ifadenin alt ifadelerinin izin verilen her sıralaması için karşılanacaktır; aksi takdirde davranış tanımsızdır.

(Bu soruyla ilgili olmayan 53 numaralı dipnota yapılan referansı atladım).

Esasen &nuaramadan önce değerlendirilmeli c1::meth1()ve nuaramadan önce değerlendirilmelidir c1::meth2(). Bununla birlikte, nudaha önce değerlendirilecek bir gereksinim yoktur &nu(örneğin, nuönce değerlendirilmesine izin verilir , daha &nusonra c1::meth1()çağrılır - derleyicinizin yaptığı şey bu olabilir). Sentezleme *ar = 1de c1::meth1(), bu nedenle garanti edilmez önce değerlendirilmesi nuolarak main()geçirilecek amacıyla değerlendirilir c1::meth2().

Daha sonra C ++ standartları (şu anda bu gece kullandığım bilgisayarda yok) esasen aynı maddeye sahip.


7

Bence derleme yaparken, meth1 ve meth2 fonksiyonları gerçekten çağrılmadan önce, parametreler onlara aktarıldı. "C.meth1 (& nu) .meth2 (nu);" kullandığınızda demek istiyorum. nu = 0 değeri meth2'ye geçirilmiştir, bu nedenle "nu" nun daha sonra değiştirilmesinin önemi yoktur.

bunu deneyebilirsin:

#include <iostream> 
class c1
{
public:
    c1& meth1(int* ar) {
        std::cout << "method 1" << std::endl;
        *ar = 1;
        return *this;
    }
    void meth2(int* ar)
    {
        std::cout << "method 2:" << *ar << std::endl;
    }
};

int main()
{
    c1 c;
    int nu = 0;
    c.meth1(&nu).meth2(&nu);
    getchar();
}

istediğiniz cevabı alacak

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.