2011-01-01 63 views
6
#include <cstdio> 
using namespace std; 

class A { 
public: 
    virtual void func() { printf("A::func()"); } 
}; 

class B : public A { 
public: 
    virtual void func() { printf("B::func()"); } 
}; 

int main() { 
    A a = *(A *)new B(); 
    a.func(); 
} 

Câu hỏi đặt ra rất đơn giản: tại sao a->func() gọi hàm trong lớp A mặc dù a chứa đối tượng của lớp B?chức năng Overriding trong C++ không hoạt động

+0

Khi lỗi cú pháp được cố định sao cho nó biên dịch, mã của bạn làm điều đúng (gọi 'B :: func()'). Vì đây không phải là mã * thực * của bạn, tôi không biết vấn đề của bạn có thể là gì! –

+0

Mã này không biên dịch (ít nhất là đối với tôi). Visual C++ 2008 Express – mnn

+0

@mnm: Ah, tôi thấy bạn đã thay đổi nó. Trên thực tế, nó vẫn không biên dịch (thiếu ';' ở cuối mỗi định nghĩa lớp). –

Trả lời

13
A a = *(A *)new B(); 
a.func(); 

Đây là những gì xảy ra trong mã này, từng bước:

  • new B(): đối tượng mới loại B được phân bổ trên cửa hàng miễn phí, dẫn đến địa chỉ
  • (A*): địa chỉ của đối tượng được đúc thành A*, vì vậy chúng tôi trỏ đến một đối tượng thuộc loại B, hợp lệ. Tất cả ok.
  • A a: tại đây sự cố bắt đầu. Một đối tượng địa phương mới loại A được tạo trên ngăn xếp và được tạo bằng cách sử dụng hàm tạo bản sao A::A(const A&), với đối tượng đầu tiên là đối tượng được tạo trước đó.
  • Con trỏ đến đối tượng gốc loại B bị mất sau tuyên bố này, dẫn đến rò rỉ bộ nhớ, vì nó được cấp phát trên cửa hàng miễn phí với new.
  • a.func() - phương pháp được gọi trên đối tượng (địa phương) của lớp A.

Nếu bạn thay đổi mã để:

A& a = *(A*) new B(); 
a.func(); 

sau đó chỉ có một đối tượng sẽ được xây dựng, con trỏ của nó sẽ được chuyển đổi sang con trỏ kiểu A*, sau đó dereferenced và tham chiếu mới sẽ được khởi tạo với địa chỉ này. Cuộc gọi của hàm ảo sau đó sẽ được giải quyết động theo số B::func().


Nhưng hãy nhớ, bạn vẫn sẽ cần phải giải phóng các đối tượng vì nó đã được phân bổ với new:

delete &a; 

Trong đó, bằng cách này, chỉ sẽ được chính xác nếu A có một destructor ảo , đó là yêu cầu mà B :: ~ B() (may mắn là có sản phẩm nào ở đây, nhưng nó không cần phải trong trường hợp chung) cũng sẽ được gọi. Nếu A không có một destructor ảo, sau đó bạn sẽ cần phải giải phóng nó bằng cách:

delete (B*)&a; 

Nếu bạn muốn sử dụng một con trỏ, thì đó là giống như với các tài liệu tham khảo. Mã số:

A* a = new B(); // actually you don't need an explicit cast here. 
a->func(); 
delete (B*)a; // or just delete a; if A has a virtual destructor. 
+1

+1 để giải phóng bộ nhớ được phân bổ bởi con trỏ. – cpx

+0

+1 cho câu trả lời được giải thích rất độc đáo. :) Chỉ cần một bình luận bên: nhiều người thích tham khảo "cửa hàng miễn phí" là "đống". – Venemo

+0

@Kos: Tôi sẽ nhấn mạnh rằng 'xóa & a;', 'xóa (B *) & a;' cũng như 'xóa (B *) a;' chỉ là thủ thuật để làm cho nó hoạt động, trong khi người ta thực sự nên tránh điều này trong thực hành và đi với con trỏ và destructors ảo. –

5

Vấn đề bạn gặp phải là cổ điển object slicing:

A a = *(A *)new B(); 

Hãy a hoặc là một tham chiếu hoặc con trỏ đến A, và công văn ảo sẽ làm việc như bạn mong đợi. Xem this other question để biết thêm giải thích.


Bạn đã nhận xét về câu trả lời khác rằng "Trình biên dịch ít nhất phải đưa ra cảnh báo hoặc những gì". Đây là lý do tại sao nó được coi là một thực hành tốt để làm cho các lớp cơ sở hoặc trừu tượng của không thể sao chép: mã ban đầu của bạn sẽ không có biên soạn ở nơi đầu tiên.

+0

Liên kết đẹp, nhưng tôi e rằng vấn đề này hoàn toàn không liên quan đến việc "cắt". Vấn đề ở đây là OP vô tình tạo ra một đối tượng khác. – Kos

+0

@Kos: ... và cắt lát đối tượng được sao chép từ trong quá trình. Dù sao, ít nhất bạn biện minh cho downvote của bạn. – icecrime

+0

Tôi sẽ cố hết sức để giải thích. :) Tôi không bị xúc phạm hay gì đó. Nó chỉ là việc cắt không xảy ra ở đây. Một đối tượng của lớp A được tạo và xây dựng một cách rõ ràng với 'A :: A (const A &)'. Cắt lát, như tôi đã hiểu, xảy ra khi chúng ta muốn 'B :: operator =', trên một đối tượng của lớp B, nhưng thay vào đó 'A :: operator =' được sử dụng và đối tượng được gán cho "một phần". Đây là đối tượng của lớp A được yêu cầu (vô ý!) Và được xây dựng đầy đủ. Vấn đề là không gọi sai op =, hoặc ctor sao chép sai - nhưng thực tế tuyệt đối mà một bản sao không mong muốn-xây dựng xảy ra. Do đó - không cắt IMO. – Kos

5

Bây giờ bạn đã sửa đổi đoạn mã của mình, vấn đề là rõ ràng. Tính đa hình (tức là các hàm ảo) chỉ được gọi thông qua con trỏ và tham chiếu. Bạn không có trong số này. A a = XXX không chứa đối tượng thuộc loại B, nó chứa đối tượng thuộc loại A. Bạn đã "cắt đi" các B -ness của đối tượng bằng cách làm con trỏ đó cast và dereference.

Nếu bạn làm A *a = new B();, thì bạn sẽ nhận được hành vi mong đợi.

+0

Cảm ơn. Điều này đã giúp. Trình biên dịch ít nhất phải đưa ra cảnh báo hoặc những gì. Tôi ghét C++ đến mức nào, tôi muốn sử dụng C#. – mnn

+0

@mnm: Tại sao cảnh báo nên cảnh báo? Đối với tất cả những gì nó biết, đó là chính xác những gì bạn có nghĩa là để làm! –

+0

Đó không thực sự là một lời giải thích tốt IMO - nó không đề cập đến rằng ** hai ** đối tượng được tạo ra ở đây và tồn tại một rò rỉ bộ nhớ. Không có thuật ngữ C++ như "cắt bỏ" (tốt, có, nhưng nó là khái niệm hơn kỹ thuật), những gì xảy ra ở đây là "xây dựng bản sao". – Kos

1

Điều này có thể làm điều đó.

A &a = *(A *)new B(); 
a.func(); 

Hoặc

A *a = new B(); 
a->func(); 
+0

Điều này cũng hoạt động, nhưng tôi nghĩ sử dụng con trỏ theo cách mà Oli Charlesworth mô tả có vẻ "sạch hơn". – mnn

+1

trong C++, * con trỏ giống như một tham chiếu (nếu không hoàn toàn là một tham chiếu), nó không phải là một giá trị. do đó, một * pointer_to_A là A &. – BatchyX

1

ảo công văn chỉ làm việc với con trỏ hoặc tham chiếu loại:

#include <cstdio> 
using namespace std; 

class A { 
public: 
    virtual void func() { printf("A::func()"); } 
}; 

class B : public A { 
public: 
    virtual void func() { printf("B::func()"); } 
}; 

int main() { 
    A* a = new B(); 
    a->func(); 
} 
+1

"chỉ hoạt động với con trỏ hoặc loại tham chiếu" - đúng, nhưng nó không thực sự "không hoạt động với các loại giá trị", mà đúng hơn là "không có ý nghĩa nhiều với các loại giá trị". Nếu chúng ta biết rằng một đối tượng chính xác là loại A (giữ cho các kiểu giá trị), thì chúng ta luôn có thể suy ra phương thức một cách chính xác trong thời gian biên dịch, vì vậy không cần phải kết nối muộn. – Kos

0

Vấn đề là sự lựa chọn và truyền B đến A với A a = * (A *) mới B();

Bạn có thể sửa lỗi bằng cách xóa * (A *) thay đổi thành (A * a = new B();) nhưng tôi sẽ tiến thêm một bước nữa vì tên biến của bạn không tốt cho việc khởi tạo B

. Nó phải là

B *b = new B(); 
b->func(); 
0

Bởi vì bạn thực hiện cắt khi bạn sao chép các đối tượng được cấp phát động vào đối tượng a loại A (mà cũng đã cho bạn một rò rỉ bộ nhớ).

a phải là tham chiếu (A&) thay vào đó, hoặc chỉ giữ con trỏ.

Các vấn đề liên quan