2010-01-20 62 views
7

Hãy xem xét tình huống này rất đơn giản:C++ chức năng ảo không được gọi trong lớp con

A.h 

class A { 
public: 
    virtual void a() = 0; 
}; 

B.h 

#include <iostream> 

class B { 
public: 
    virtual void b() {std::cout << "b()." << std::endl;}; 
}; 

C.h 

#include "A.h" 
#include "B.h" 

class C : public B, public A { 
public: 
    void a() {std::cout << "a() in C." << std::endl;}; 
}; 

int main() { 
    B* b = new C(); 
    ((A*) b)->a(); // Output: b(). 

    A* a = new C(); 
    a->a();   // Output:: a() in C. 

    return 0; 
} 

Nói cách khác:
- A là một lớp ảo tinh khiết .
- B là một lớp không có siêu lớp và một hàm ảo không thuần túy.
- C là một phân lớp của A và B và ghi đè chức năng ảo thuần túy của A.

gì làm tôi ngạc nhiên là sản phẩm đầu tiên ví dụ:

((A*) b)->a(); // Output: b(). 

Mặc dù tôi gọi một() trong mã, b() được gọi. Tôi đoán là nó liên quan đến thực tế là biến b là một con trỏ đến lớp B mà không phải là một lớp con của lớp A. Nhưng vẫn là kiểu thời gian chạy là một con trỏ tới một cá thể C.

Quy tắc C++ chính xác để giải thích điều này, từ quan điểm Java, hành vi kỳ lạ là gì?

+2

Đặt đơn giản là: ** B không phải là A! ** Chúng hoàn toàn không liên quan với nhau, nhưng của bạn (không tốt để sử dụng!) C-phong cách diễn viên không quan tâm một trong hai cách. 'dynamic_cast' sẽ duyệt chính xác hệ thống phân cấp của bạn. Khi bạn truyền các loại con trỏ không liên quan, bạn sẽ nhận được hành vi không xác định. Điều đó có nghĩa là bất cứ điều gì có thể xảy ra, từ nó dường như làm việc để thổi máy tính của bạn lên. – GManNickG

+0

Đừng quên những con quỷ mũi. –

Trả lời

24

Bạn đang vô điều kiện đúc b đến A* bằng cách sử dụng kiểu đúc C. Trình biên dịch không ngăn bạn làm điều này; bạn nói đó là số A* vì vậy đây là số A*. Vì vậy, nó xử lý bộ nhớ nó trỏ đến như một thể hiện của A. Kể từ a() là phương pháp đầu tiên được liệt kê trong vnable Ab() là phương pháp đầu tiên được liệt kê trong vnable B, khi bạn gọi a() trên một đối tượng thực sự là B, bạn nhận được b().

Bạn đang may mắn khi bố cục đối tượng tương tự. Điều này không được đảm bảo là trường hợp.

Trước tiên, bạn không nên sử dụng phôi kiểu C. Bạn nên sử dụng C++ casting operators có độ an toàn cao hơn (mặc dù bạn vẫn có thể tự bắn mình vào chân, vì vậy hãy đọc kỹ tài liệu).

Thứ hai, bạn không nên dựa vào loại hành vi này, trừ khi bạn sử dụng dynamic_cast<>.

+0

Bạn hoàn toàn có thể dựa vào việc truyền chéo nếu 'dynamic_cast' được sử dụng. Đó là 100% đảm bảo để làm việc hoặc tiền của bạn trở lại. – coppro

+0

@coppro: nếu bạn sử dụng dynamic_cast, vâng. Tôi đã cố gắng nhấn mạnh không dựa vào việc này với dàn diễn viên kiểu C. Tôi đã cập nhật tuyên bố cuối cùng của mình để làm rõ hơn. –

+0

+1 để được may mắn. –

11

Không sử dụng dàn diễn viên kiểu C khi truyền trên cây đa thừa kế. Nếu bạn sử dụng dynamic_cast thay vào đó bạn có được kết quả mong đợi:

B* b = new C(); 
dynamic_cast<A*>(b)->a(); 
+0

Đây phải là lỗi thời gian chạy trong ví dụ đã cho. Chính xác? –

+0

@tehMick, không có hành vi nào được xác định là – Trent

+0

@teh @Trent: Cả hai đều sai. : P Nó hoạt động chính xác, đi qua hệ thống phân cấp thừa kế. – GManNickG

2

((A*) b) là một diễn viên c-phong cách rõ ràng, được phép không có vấn đề gì các loại chỉ vào được. Tuy nhiên, nếu bạn cố gắng dereference con trỏ này, nó sẽ là một lỗi thời gian chạy hoặc hành vi không thể đoán trước. Đây là một ví dụ của sau này. Đầu ra bạn quan sát là không có nghĩa là an toàn hoặc được bảo đảm.

5

Bạn đang bắt đầu bằng B * và truyền nó thành A *. Kể từ khi hai không liên quan, bạn đang delving vào lĩnh vực hành vi không xác định.

0

Tôi nghĩ rằng bạn có một lỗi tinh vi khi truyền từ B* đến A* và hành vi không xác định.Tránh sử dụng phôi kiểu C và thích phôi C++ - trong trường hợp này là dynamic_cast. Do cách trình biên dịch của bạn đã đặt ra lưu trữ cho các kiểu dữ liệu và các mục nhập vtable, bạn đã kết thúc việc tìm địa chỉ của một hàm khác.

1

Các dòng sau là một reinterpret_cast, mà chỉ cùng bộ nhớ nhưng "giả vờ" đó là một loại khác nhau của đối tượng:

((A*) b)->a(); 

gì bạn thực sự muốn là một dynamic_cast, trong đó kiểm tra những loại đối tượng b thực sự là những gì và điều chỉnh vị trí trong bộ nhớ để trỏ đến:

dynamic_cast<A*>(b)->a() 

Như jeffamaphone đề cập, cách bố trí tương tự của hai lớp là những gì làm cho chức năng sai lầm khi được gọi.

1

Có hầu như không bao giờ có dịp trong C++ khi sử dụng dàn diễn viên kiểu C (hoặc tương đương C++ tương đương reinterpret_cast <>) là hợp lý hoặc bắt buộc. Bất cứ khi nào bạn thấy mình bị cám dỗ để sử dụng một trong hai, nghi ngờ mã của bạn và/hoặc thiết kế của bạn.

2

AB có liên quan với nhau bằng phương pháp kế thừa, có nghĩa là một con trỏ đến B không thể được chuyển đổi thành một con trỏ đến A bằng phương tiện của một trong hai bị ném lên trời hay nhìn xuống.

Kể từ AB hai căn cứ khác nhau của C, những gì bạn đang cố gắng làm ở đây được gọi là một cross-cast. Các diễn viên duy nhất trong ngôn ngữ C + + có thể thực hiện một cross-cast là dynamic_cast. Đây là những gì bạn phải sử dụng trong trường hợp này trong trường hợp bạn thực sự cần nó (bạn?)

B* b = new C(); 
A* a = dynamic_cast<A*>(b); 
assert(a != NULL); 
a->a();  
Các vấn đề liên quan