2011-02-04 8 views
167

Với định nghĩa struct đưa ra dưới đây ...Từ khóa C++ "ảo" cho các hàm trong các lớp dẫn xuất. Có cần thiết không?

struct A { 
    virtual void hello() = 0; 
}; 

Approach # 1:

struct B : public A { 
    virtual void hello() { ... } 
}; 

Tiếp cận # 2:

struct B : public A { 
    void hello() { ... } 
}; 

Có sự khác biệt giữa hai cách để ghi đè hàm hello?

+55

Trong C++ 11 bạn có thể viết "void hello() override {}" để khai báo rõ ràng rằng bạn đang ghi đè phương thức ảo. Trình biên dịch sẽ thất bại nếu một phương thức ảo cơ sở không tồn tại, và nó có khả năng đọc giống như đặt "ảo" trên lớp con cháu. – ShadowChaser

+0

Thực ra, trong C++ 11 của gcc, viết void hello() override {} trong lớp dẫn xuất là tốt vì lớp cơ sở đã xác định rằng phương thức hello() là ảo. Nói cách khác, việc sử dụng từ ảo trong lớp _derived_ là không cần thiết/bắt buộc, cho gcc/g ++. (Tôi đang sử dụng gcc phiên bản 4.9.2 trên một RPi 3) Nhưng thực tế tốt là bao gồm từ khóa ảo trong phương thức của lớp dẫn xuất. – Will

Trả lời

138

Chúng giống hệt nhau. Không có sự khác biệt giữa chúng khác với cách tiếp cận đầu tiên đòi hỏi phải gõ nhiều hơn và có khả năng rõ ràng hơn.

+18

Điều này đúng, nhưng [Hướng dẫn về khả năng di chuyển của Mozilla C++] (https://developer.mozilla.org/en/C___Portability_Guide#Use_virtual_declaration_on_all_subclass_virtual_member_functions) đề xuất luôn sử dụng ảo vì "một số trình biên dịch" cảnh báo nếu bạn không. Quá xấu họ không đề cập đến bất kỳ ví dụ về trình biên dịch như vậy. –

+4

Tôi cũng sẽ thêm rằng đánh dấu rõ ràng nó là ảo sẽ giúp nhắc nhở bạn để làm cho destructor ảo là tốt. – lfalin

+1

Chỉ đề cập đến, tương tự áp dụng cho [destructor ảo] (http: // stackoverflow.com/questions/2198379/are-virtual-destructors-inherited) – Atul

7

Không có sự khác biệt đối với trình biên dịch, khi bạn viết virtual trong lớp dẫn xuất hoặc bỏ qua nó.

Nhưng bạn cần xem xét lớp cơ sở để nhận thông tin này. Vì vậy, tôi khuyên bạn nên thêm từ khóa virtual cũng trong lớp dẫn xuất, nếu bạn muốn hiển thị cho con người rằng hàm này là ảo.

67

'ảo' của hàm được truyền ngầm, tuy nhiên ít nhất một trình biên dịch tôi sử dụng sẽ tạo cảnh báo nếu từ khóa virtual không được sử dụng rõ ràng, vì vậy bạn có thể muốn sử dụng nó nếu chỉ giữ trình biên dịch yên lặng.

Từ quan điểm hoàn toàn theo kiểu phong cách, bao gồm từ khóa virtual rõ ràng 'quảng cáo' thực tế cho người dùng rằng hàm là ảo. Điều này sẽ rất quan trọng đối với bất kỳ ai tiếp tục phân lớp B mà không phải kiểm tra định nghĩa của A. Đối với các cấu trúc phân cấp sâu, điều này trở nên đặc biệt quan trọng.

+9

Trình biên dịch này là gì? –

+27

@James: armcc (trình biên dịch của ARM cho các thiết bị ARM) – Clifford

+1

Thú vị. Cảm ơn vì thông tin. –

9

Thêm từ khóa "ảo" là thực hành tốt vì nó cải thiện khả năng đọc, nhưng không cần thiết. Các hàm được khai báo ảo trong lớp cơ sở và có cùng chữ ký trong các lớp dẫn xuất được coi là "ảo" theo mặc định.

0

Tôi chắc chắn sẽ bao gồm từ khóa ảo cho lớp con, vì i. Dễ đọc. ii. Lớp con này của tôi được dẫn xuất xa hơn, bạn không muốn hàm tạo của lớp dẫn xuất tiếp theo gọi hàm ảo này.

+0

Tôi không hiểu điểm 'ii'. Bạn có thể mở rộng trên nó? –

+0

Tôi nghĩ rằng anh ta có nghĩa là không đánh dấu chức năng con là ảo, một lập trình viên xuất phát từ lớp con sau này có thể không nhận ra rằng hàm thực sự là ảo (vì anh ta chưa bao giờ nhìn vào lớp cơ sở) và có khả năng gọi nó trong khi xây dựng (có thể hoặc không thể làm điều đúng). – PfhorSlayer

29

Từ khóa virtual không cần thiết trong lớp dẫn xuất. Dưới đây là các tài liệu hỗ trợ, từ C++ Dự thảo tiêu chuẩn (N3337) (tôi nhấn mạnh):

10,3 chức năng ảo

2 Nếu một hàm thành viên ảo vf được khai báo trong một lớp Base và trong một lớp học Derived, được lấy trực tiếp hoặc gián tiếp từ Base, chức năng thành viên vf có cùng tên, thông số kiểu danh sách (8.3.5), cv-qualification và ref-qualifier (hoặc vắng mặt) như Base::vf được khai báo, sau đó Derived::vf cũng là ảo (dù có hay không ot nó là như vậy tuyên bố) và nó ghi đè Base::vf.

23

Không, không cần phải nhập từ khóa virtual từ khóa trên lớp học hàm bắt nguồn '.Nhưng nó là đáng nói đến một pitfall liên quan: một thất bại để ghi đè lên một chức năng ảo.

Không thể ghi đè không thể ghi đè nếu bạn có ý định ghi đè hàm ảo trong lớp dẫn xuất nhưng tạo lỗi trong chữ ký sao cho nó khai báo hàm ảo mới và khác. Hàm này có thể là quá tải của hàm lớp cơ sở hoặc có thể khác nhau về tên. Cho dù bạn có sử dụng từ khóa virtual trong khai báo hàm lớp dẫn xuất hay không, trình biên dịch sẽ không thể nói rằng bạn định ghi đè hàm từ lớp cơ sở.

cạm bẫy này, tuy nhiên, may mắn giải quyết bằng 11 explicit override C++ tính năng ngôn ngữ, cho phép mã nguồn để xác định rõ ràng rằng một hàm thành viên được thiết kế để ghi đè lên một chức năng lớp cơ sở:

struct Base { 
    virtual void some_func(float); 
}; 

struct Derived : Base { 
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method 
}; 

Trình biên dịch sẽ phát hành một lỗi thời gian biên dịch và lỗi lập trình sẽ được hiển thị ngay lập tức (có lẽ hàm trong Derived phải lấy một đối số) là float.

Tham khảo WP:C++11.

1

Có sự khác biệt đáng kể khi bạn có các mẫu và bắt đầu tham gia lớp cơ sở (es) như mẫu tham số (s):

struct None {}; 

template<typename... Interfaces> 
struct B : public Interfaces 
{ 
    void hello() { ... } 
}; 

struct A { 
    virtual void hello() = 0; 
}; 

template<typename... Interfaces> 
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly 
{ 
    b.hello(); // indirect, non-virtual call 
} 

void hello(const A& a) 
{ 
    a.hello(); // Indirect virtual call, inlining is impossible in general 
} 

int main() 
{ 
    B<None> b;   // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually 
    B<None>* pb = &b; 
    B<None>& rb = b; 

    b.hello();   // direct call 
    pb->hello();  // pb-relative non-virtual call (1 redirection) 
    rb->hello();  // non-virtual call (1 redirection unless optimized out) 
    t_hello(b);   // works as expected, one redirection 
    // hello(b);  // compile-time error 


    B<A>  ba;  // Ok, vtable generated, sizeof(b) >= sizeof(void*) 
    B<None>* pba = &ba; 
    B<None>& rba = ba; 

    ba.hello();   // still can be a direct call, exact type of ba is deducible 
    pba->hello();  // pba-relative virtual call (usually 3 redirections) 
    rba->hello();  // rba-relative virtual call (usually 3 redirections unless optimized out to 2) 
    //t_hello(b);  // compile-time error (unless you add support for const A& in t_hello as well) 
    hello(ba); 
} 

Các phần thú vị của nó là bây giờ bạn có thể định nghĩa giao diện và phi giao diện chức năng sau để xác định các lớp. Điều đó rất hữu ích cho các giao diện interworking giữa các thư viện (không dựa vào điều này như là một quy trình thiết kế chuẩn của một thư viện đơn). Bạn không mất chi phí để cho phép điều này cho tất cả các lớp học của mình - thậm chí bạn có thể typedef B để làm điều gì đó nếu bạn muốn. Lưu ý rằng, nếu bạn làm điều này, bạn có thể muốn khai báo các nhà xây dựng sao chép/di chuyển như các khuôn mẫu: quá cho phép xây dựng từ các giao diện khác nhau cho phép bạn 'bỏ' giữa các loại khác nhau B<>.

Điều đáng ngờ là bạn nên thêm hỗ trợ cho const A& trong t_hello(). Lý do thông thường cho việc viết lại này là di chuyển ra khỏi chuyên môn dựa trên kế thừa dựa trên mẫu, chủ yếu là vì lý do hiệu suất. Nếu bạn tiếp tục hỗ trợ giao diện cũ, bạn khó có thể phát hiện (hoặc ngăn chặn) việc sử dụng cũ.