2011-12-20 24 views
5

Ben benzer bir şey bugün tökezledi döküm ve akabinde bir kaç şey denedik ve şu ++ G yasal olarak görünüyor fark: YaniC++ atama

struct A { 
    int val_; 
    A() { } 
    A(int val) : val_(val) { } 
    const A& operator=(int val) { val_ = val; return *this; } 
    int get() { return val_; } 
}; 

struct B : public A { 
    A getA() { return (((A)*this) = 20); } // legal? 
}; 

int main() { 
    A a = 10; 
    B b; 
    A c = b.getA(); 
} 

B::getB bir tür A döndürür ondan sonra 20 değerini kendisine atanmış olarak (aşırı yüklü A::operator= aracılığıyla).

Birkaç testten sonra, doğru değeri döndürdüğü anlaşılıyor (c.get, beklendiği gibi 20 döndürecektir).

Yani merak ediyorum, bu tanımsız davranış mı? Eğer durum buysa, bunu tam olarak yapan nedir? Değilse, bu tür kodun avantajları ne olurdu?

+2

'const A & operator = (int val) {val_ = val; } bir dönüş beyanı eksik? – ruakh

+1

Eh, B :: getB' yok, ancak B :: getA' B örneğini değil (A'nın bir alt sınıfı olan) B örneğini döndürür. Çok belirsiz bir koddur, ancak yüzey, mükemmel okunaklı. –

+0

@ruakh: Öyle görünüyor. :) – netcoder

cevap

3

:

return (((A)*this) = 20); 

... steno gibidir (henüz belirsiz) sözdizimi için: daha iyi

A a(*this); 
return a.operator=(20); 

... ya:

... ve bu nedenle davranış tanımlanmıştır.

+1

Evet, ve ben de 'return A (* this) = 20;' yazıyordum. Bu hiç de karanlık değil ve kodun ne kadar tehlikeli olduğunu açıkça gösteriyor! :-) Belirsiz olan orijinal sözdizimidir! –

+0

@KerrekSB:% 100 katılıyorum. – netcoder

+0

+1. "... ve bu nedenle davranış tanımlanmıştır." Tanımlandı, * eğer * dönüş tipi referans değil. (Bunu cevaba eklemenize gerek yok, ana noktaya teğet ve ben sizin cevabınızı benimki kadar istemiyorum!) –

1

Burada yapılacak çok farklı şeyler var. Kod geçerlidir, ancak sorunuzda yanlış bir varsayım yaptınız. Sen

"B :: Geta döner [...] kendisine değer 20 atanan, ondan sonra sıra"

(vurgu bana ait) Bu, doğru olmadığını söyledi. getA, nesneyi değiştirmek için değiştirmez. Bunu doğrulamak için, yöntem imzasına const'u yerleştirebilirsiniz. O zaman tam olarak anlatacağım.

A getA() const { 
    cout << this << " in getA() now" << endl; 
    return (((A)*this) = 20); 
} 

Burada neler oluyor? Benim sample code baktığımızda (Bu cevap sonuna benim transkript kopyaladıktan):

A a = 10; 

Bu yapıcı bir A bildirir. Oldukça dürüst. Bu bir sonraki satır:

B b; b.val_ = 15; 

B herhangi kurucular yok, bu yüzden (A miras) onun val_ üyesine doğrudan yazmak zorunda. o superfically öyle gibi görünebilir, ancak bu b değiştirmez

b.getA(); 

: Önümüzdeki çizgi A c = b.getA(); düşünmeden önce

, çok dikkatli daha basit ifade göz önüne almalıyız. Sonunda

, benim örnek kod b.val_ yazdırır ve hala 15 eşit olduğunu görüyoruz. 20'ye değişmedi. c.val_ elbette 20'ye dönüştü.

Bak getA içinde ve (((A)*this) = 20) bkz. Bunu kıralım:

this  // a pointer to the the variable 'b' in main(). It's of type B* 
*this // a reference to 'b'. Of type B& 
(A)*this // this copies into a new object of type A. 

Burada duraklamaya değer. Bu (A&)*this veya hatta *((A*)this) ise, daha basit bir çizgi olur. Ama bu (A)*this ve bu nedenle bu A tipi yeni bir nesne oluşturur ve ilgili dilimi b'den b'ye kopyalar.

(Ekstra: Dilimin nasıl kopyalanabileceğini sorabilirsiniz. B& referansımız var ve yeni bir A oluşturmak istiyoruz.Varsayılan olarak, derleyici bir kopya oluşturucu A :: A (const A&) oluşturur. Derleyici, bir başvuru B&, bir const A& için doğal olarak dökülebilir çünkü kullanabilirsiniz.)

Özellikle this != &((A)*this). Bu sana bir sürpriz olabilir. (Ekstra: Öte yandan this == &((A&)*this) genellikle ( virtual yöntemleri) olup olmamasına bağlı olarak) Şimdi bu yeni bir nesne olduğunu

, biz bu bu yeni değere sayısını koyar

((A)*this) = 20 

bakabilirsiniz . Bu ifade, numaralı numaralı this->val_ etkilemez.

getA'u A& döndürecek şekilde değiştirmek bir hata olur. İlk olarak, operator= dönüş değeri const A& olup, bu nedenle A& olarak döndüremezsiniz. Ancak dönüş türü olarak const A& olsaydınız bile, bu getA içinde oluşturulan geçici bir yerel değişkenin referansı olur. Böyle şeyleri iade etmek doğru değildir.

Son olarak, Geta değeri tarafından kopya döndüren geçerli kod, güvende ve iyi olmasının nedeni c olan Geta

A c = b.getA(); 

dan değeriyle döndürülür bu kopyayı alacağını görebilirsiniz -defined.

== aşağıdaki @Kerrek SB ve @Aaron McDaid, yardımı ile dikkatli incelemeden sonra tam programı ==

#include <iostream> 
using namespace std; 
struct A { 
    int val_; 
    A() { } 
    A(int val) : val_(val) { } 
    const A& operator=(int val) { 
     cout << this << " in operator= now" << endl; // prove the operator= happens on a different object (the copy) 
     val_ = val; 
     return *this; 
    } 
    int get() { return val_; } 
}; 

struct B : public A { 
    A getA() const { 
     cout << this << " in getA() now" << endl; // the address of b 
     return (((A)*this) = 20); 
      // The preceding line does four things: 
      // 1. Take the current object, *this 
      // 2. Copy a slice of it into a new temporary object of type A 
      // 3. Assign 20 to this temporary copy 
      // 4. Return this by value 
    } // legal? Yes 
}; 

int main() { 
    A a = 10; 
    B b; b.val_ = 15; 
    A c = b.getA(); 
    cout << b.get() << endl; // expect 15 
    cout << c.get() << endl; // expect 20 
    B* b2 = &b; 
    A a2 = *b2; 
    cout << b2->get() << endl; // expect 15 
    cout << a2.get() << endl; // expect 15 

} 
+0

+1, size daha fazlasını veririm ama yapamam. Ancak, cevabınız kendi iyiliği için çok karmaşık bir yoldur ... :) – netcoder

+0

Ve bir başka pedantry parçası aşağıdaki gibidir: getA'nın dönüş türünü "A &" olarak değiştirin ve işlevin dönüş türünü değiştirin. Ayrıca &. Kod derlenecek ve çalıştırılacak, ancak tehlikeli.Bunu görmek için başka bir değişiklik yapın: '= 20'yi silin ve' return ((A) * this ') kullanın, 'Aldığınız hata mesajı bunun geçici olduğunu onaylayacaktır. "hata: A ve" türünün, "A" türü için geçici olmayan başvurunun geçersiz başlatılması "A" " –

+0

@netcoder türünde. Anlaşıldı, LOL! –