2016-06-19 19 views
5

tôi có một kết quả mà tôi không mong đợi từ nhiều thừa kế, virtual phương pháp và con trỏ đến các lớp học cơ sở.Nhiều thừa kế, phương pháp ảo va chạm và gợi ý từ các lớp cơ sở


Với d.getStr(), khi d là một trường hợp derived, phiên bản base_2 được gọi là, như tôi mong đợi.

Với p->getStr(), khi p là một con trỏ đến một trường hợp derived (hoặc một con trỏ đến base_2 trỏ đến một trường hợp derived), phiên bản base_2 được gọi là, như tôi mong đợi.

Nhưng với p->getStr(), khi p là một con trỏ đến một base_1 trỏ đến một trường hợp derived, phiên bản base_1 được gọi và tôi đã thuyết phục sẽ được gọi là phiên bản base_2 (nhờ các using và thực tế là getStr()virtual phương pháp) .

Sau đây là một ví dụ đơn giản:

#include <iostream> 

struct base_1 
{ 
    virtual std::string getStr() const 
    { return "string from base 1"; } 
}; 

struct base_2 
{ 
    virtual std::string getStr() const 
    { return "string from base 2"; } 
}; 

struct derived : public base_1, public base_2 
{ 
    using base_2::getStr; 
}; 


int main() 
{ 
    derived d; 

    derived * dp = &d; 
    base_1 * bp1 = &d; 
    base_2 * bp2 = &d; 

    std::cout << "from derived:   " << d.getStr() << std::endl; 
    std::cout << "from derived pointer: " << dp->getStr() << std::endl; 
    std::cout << "from base_1 pointer: " << bp1->getStr() << std::endl; 
    std::cout << "from base_2 pointer: " << bp2->getStr() << std::endl; 
} 

Kết quả là sau

from derived:   string from base 2 
from derived pointer: string from base 2 
from base_1 pointer: string from base 1 
from base_2 pointer: string from base 2 

Tôi biết rằng, để áp đặt cuộc gọi của phiên bản base_2, tôi có thể thêm vào derived các phương pháp sau đây

std::string getStr() const 
{ return base_2::getStr(); } 

nhưng câu hỏi của tôi là:

1) Tại sao con trỏ đến base_1 (trỏ đến một thể hiện có nguồn gốc) bỏ qua chỉ thị using và gọi phiên bản base_1 của getStr()?

2) Có cách nào để áp đặt các phiên bản base_2 của getStr(), khi derived dụ được sử dụng bởi một con trỏ base_1, mà không cần xác định lại getStr()?

--- EDIT ---

Cảm ơn câu trả lời.

tôi hiểu rằng bạn đang mô tả những gì đang xảy ra nhưng nghi ngờ của tôi là: không ngôn ngữ (tiêu chuẩn) mô tả khía cạnh này? Hay nó là một phần không xác định?

tôi muốn nói: nếu tôi loại bỏ các chỉ thị using, tôi nhận được một lỗi biên dịch (error: request for member getStr is ambiguous), từ d.getStr() và từ dp->getStr(), bởi vì trình biên dịch không biết phiên bản nào của getStr() để chọn.

Nhưng getStr()virtual phương pháp. Vì vậy, (tôi đã bị thuyết phục rằng) một con trỏ cơ sở nên sử dụng phiên bản có nguồn gốc của họ. Nhưng chúng tôi có một vài phương pháp va chạm.

Từ quan điểm ngôn ngữ (tiêu chuẩn), base_1 (hoặc base_2) là con trỏ được ủy quyền (hoặc bắt buộc) để chọn một trong hai phiên bản của phương thức va chạm bỏ qua phương thức khác?

Có lẽ tôi sai nhưng dường như với tôi rằng, theo cách này, phương pháp virtual được quản lý như phương pháp không virtual.

+1

'using' chỉ giúp hiển thị. Nó không làm cho 'overload' bắt nguồn từ hàm ảo cơ sở hoặc trông giống như nó. – Arunmu

Trả lời

0

1) Có vẻ như mã của bạn đang làm chính xác những gì nó phải làm. Bạn trỏ đến base_1, vì vậy bạn nhận được hàm từ base_1 (hoặc bất kỳ lớp cơ sở nào của nó). Đó là đối tượng được tạo thành từ cả base_1 và base_2 là không xác định tại thời điểm đó, bởi vì bạn đang trỏ đến một lớp cơ sở, không phải là một lớp dẫn xuất.

2) Không, điều đó đơn giản là không thể. Bạn phải thực sự quá tải getStr() trong derived.

+0

cảm ơn cho anser nhưng, về điểm (1), bạn đang mô tả hành vi của các lớp cơ sở/có nguồn gốc với các phương thức bình thường; 'getStr()' là các phương thức 'virtual' – max66

+0

Điều đó không quan trọng; thực tế là các hàm ảo có thể "nhảy" từ các lớp nằm cạnh nhau, chỉ theo chiều dọc nếu bạn vẽ một biểu đồ quan hệ lớp. – JvO

4

Bạn đang mong đợi rằng khi bạn sử dụng từ khóa using theo cách sau đây:

struct derived : public base_1, public base_2 
{ 
    using base_2::getStr; 
}; 

Đó đây là giống như:

struct derived : public base_1, public base_2 
{ 
    void getStr() 
    { 
     base_2::getStr(); 
    } 
}; 

Trong trường hợp này, hành vi mà bạn đang mong đợi - - gọi p->getStr(), khi p là một con trỏ đến một số base_1 - thực ra, cuối cùng sẽ gọi base_2::getStr(). derived ghi đè base_1 's getStr(), vì vậy cách gọi base_1' s getStr(), thông qua một con trỏ bình thường, kết quả trong derived 's getStr() bị gọi, mà gọi base_2getStr() phương pháp.

Tuy nhiên, đây không phải là điều xảy ra. Từ khóa using không phải là bí danh để chuyển tiếp cuộc gọi phương thức theo cách này. Từ khóa using không tạo phương thức trong lớp học derived 'd, vì vậy kế thừa lớp không bị ảnh hưởng và derived_1' s getStr() không bị ghi đè lên trong subclas. Và đó là lý do tại sao gọi số getStr() của derived_1 không kêu gọi 's getStr().

2

Điều đó xảy ra vì lớp dẫn xuất phải có 2 mục nhập vtable cho getStr(), một cho mỗi lớp cơ sở, vì vậy nó có thể giải quyết chính xác cả hai base_1::getStr()base_2::getStr(). Chỉ thị using không tạo mục nhập vtable derived::getStr() hoặc thay thế các mục cơ sở, nó chỉ chọn mục nhập cấp cơ sở sẽ được sử dụng. Khi đi qua một con trỏ tới base_1, trình biên dịch chỉ "thấy" các mục nhập vtable cho các chức năng ảo từ derivedbase_1 để nó giải quyết getStr() đến base_1::getStr(). Giải pháp của bạn về việc thêm getStr() rõ ràng trong derived có lẽ là giải pháp rõ ràng nhất, mặc dù nó có thể được khuyến khích để làm cho nó ảo để phù hợp với các lớp cơ sở để rõ ràng.

+0

cảm ơn câu trả lời; nghi ngờ của tôi là: 2 vtables trong lớp dẫn xuất được áp đặt bởi tiêu chuẩn C++ hoặc là một chi tiết thực hiện?Nói cách khác: điều này xảy ra bởi vì nó được áp đặt bởi các tiêu chuẩn hoặc là trình biên dịch khác được ủy quyền để tạo ra một hành vi khác nhau? – max66

+0

Các cơ chế là một chi tiết thực hiện, nhưng vì các lớp cơ sở có thể tự bắt nguồn các lớp với các phương thức ảo nên kết quả cuối cùng phải làm việc khá giống nhau bất kể cơ chế chính xác. Cách thay thế duy nhất là cho trình biên dịch xem tất cả mã và có thể tìm ra loại thực tế của đối tượng được trỏ đến là gì, điều này rất không quan trọng. –

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