2011-12-20 40 views
5

tôi stumbled khi một cái gì đó tương tự như ngày hôm nay, và sau đó thử một vài thứ ra và nhận thấy rằng sau có vẻ là hợp pháp ở G ++:C++ phân vào loại đúc

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

Vì vậy B::getB trả về một loại A, sau khi nó như được gán giá trị 20 cho chính nó (thông qua quá tải A::operator=).

Sau một vài bài kiểm tra, có vẻ như nó trả về giá trị chính xác (c.get sẽ trả lại 20 như một người có thể mong đợi).

Vì vậy, tôi tự hỏi, đây có phải là hành vi không xác định? Nếu đây là trường hợp, những gì chính xác làm cho nó như vậy? Nếu không, những gì sẽ là những lợi thế của mã như vậy?

+2

Là 'const A & operator = (int val) {val_ = val; } 'thiếu câu lệnh trả về? – ruakh

+1

Vâng, không có 'B :: getB', nhưng' B :: getA' trả về một thể hiện của B (là một lớp con của A), không phải là một cá thể của A. Đó là mã rất mơ hồ, nhưng dường như, trên bề mặt, hoàn toàn hợp pháp. –

+0

@ruakh: Có vẻ như vậy. :) – netcoder

Trả lời

3

Sau khi kiểm tra cẩn thận, với sự giúp đỡ của @Kerrek SB và @Aaron McDaid, như sau:

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

... cũng giống như viết tắt (chưa che khuất) cú pháp để:

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

... hoặc thậm chí tốt hơn:

return A(*this) = 20; 

... và do đó được xác định là hành vi.

+1

Có, và tôi thậm chí còn viết 'return A (* this) = 20; '. Đó không phải là tối nghĩa, và nó làm cho nó rõ ràng mã nguy hiểm như thế nào! :-) Làm mờ là cú pháp ban đầu của bạn! –

+0

@KerrekSB: Tôi đồng ý 100%. – netcoder

+0

+1. "... và do đó được xác định hành vi." Defined, * if * kiểu trả về không phải là một tham chiếu. (Không cần phải thêm câu trả lời này vào câu trả lời, nó tiếp tuyến với điểm chính và tôi không muốn đưa ra câu trả lời của bạn miễn là của tôi!) –

1

Có một số điều khá riêng biệt đang diễn ra tại đây. Mã này là hợp lệ, tuy nhiên bạn đã đưa ra một giả định không chính xác trong câu hỏi của bạn. Bạn nói

"lợi nhuận B :: geta [...], sau đó theo sự phân công giá trị 20-tự"

(tôi nhấn mạnh) Đây không phải là chính xác. getA không không sửa đổi đối tượng. Để xác minh điều này, bạn có thể chỉ cần đặt const trong chữ ký phương thức. Sau đó tôi sẽ giải thích đầy đủ.

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

Vậy điều gì đang xảy ra ở đây? Nhìn vào số sample code của tôi (Tôi đã sao chép bảng điểm của tôi vào cuối câu trả lời này):

A a = 10; 

Điều này tuyên bố A với hàm tạo. Khá thẳng. Đây dòng tiếp theo:

B b; b.val_ = 15; 

B không có bất kỳ nhà thầu, vì vậy tôi phải viết trực tiếp cho thành viên val_ của nó (kế thừa từ A).

Trước khi chúng tôi xem xét các dòng tiếp theo, A c = b.getA();, chúng ta phải rất cẩn thận xem xét các biểu hiện đơn giản hơn:

b.getA(); 

này không sửa đổi b, mặc dù nó superfically có thể trông giống như nó.

Cuối cùng, mã mẫu của tôi in ra b.val_ và bạn thấy rằng nó bằng 15 vẫn. Nó đã không thay đổi thành 20. c.val_ đã thay đổi thành 20 tất nhiên.

Nhìn vào bên trong getA và bạn thấy (((A)*this) = 20). Hãy chia nhỏ phần này:

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. 

Nó đáng để tạm dừng ở đây. Nếu đây là (A&)*this hoặc thậm chí *((A*)this) thì đó sẽ là một dòng đơn giản hơn. Nhưng đó là (A)*this và do đó điều này tạo ra một đối tượng mới của loại A và sao chép các slice có liên quan từ b vào nó.

(Thêm: Bạn có thể hỏi cách sao chép lát. Chúng tôi có tài liệu tham khảo B& và chúng tôi muốn tạo một A mới.Theo mặc định, trình biên dịch tạo ra một hàm tạo bản sao A :: A (const A&). Trình biên dịch có thể sử dụng điều này vì một tham chiếu B& có thể được đúc tự nhiên thành const A&.)

Cụ thể this != &((A)*this). Điều này có thể gây bất ngờ cho bạn. (Giảm: Mặt khác this == &((A&)*this) thường (tùy thuộc vào việc có virtual phương pháp))

Bây giờ chúng ta có đối tượng mới này, chúng ta có thể nhìn vào

((A)*this) = 20 

Điều này khiến số lượng vào giá trị mới này . Tuyên bố này không không ảnh hưởng đến this->val_.

Sẽ là một lỗi khi thay đổi getA sao cho nó trả lại A&. Trước hết, giá trị trả lại của operator=const A& và do đó bạn không thể trả lại giá trị này là A&. Nhưng ngay cả khi bạn có const A& làm kiểu trả về, thì đây sẽ là tham chiếu đến biến cục bộ tạm thời được tạo bên trong getA. Nó là không xác định để trả lại những thứ như vậy.

Cuối cùng, chúng ta có thể thấy rằng c sẽ mất bản sao này được trả về bởi giá trị từ geta

A c = b.getA(); 

Đó là lý do tại sao các mã hiện tại, nơi geta trả về bản sao bởi giá trị, là an toàn và tốt -định nghĩa.

== Chương trình đầy đủ ==

#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 cho nỗ lực của bạn, tôi sẽ cung cấp cho bạn nhiều hơn nhưng tôi không thể. Tuy nhiên, câu trả lời của bạn là quá phức tạp vì nó là tốt ... :) – netcoder

+0

Và một phần khác của pedantry như sau: Thay đổi kiểu trả về của getA thành 'A &', và thay đổi kiểu trả về của 'operator =' thành ' A & 'cũng vậy. Mã sẽ biên dịch và chạy, nhưng nó nguy hiểm.Để thấy điều này, hãy thực hiện một thay đổi khác: xóa '= 20' và chỉ cần sử dụng' return ((A) * this); 'Thông báo lỗi bạn nhận được sẽ xác nhận rằng đó là tạm thời. "error: khởi tạo không hợp lệ tham chiếu không const của loại 'A &' từ tạm thời của loại‘ A ’" –

+0

@netcoder. Đồng ý, LOL! –