2010-06-03 12 views
13

Khi một phương pháp được khai báo là virtual trong một lớp học, ghi đè trong các lớp thừa kế sẽ được tự động coi virtual là tốt, và ngôn ngữ C++ làm cho từ khóa này virtual bắt buộc trong trường hợp này:Tại sao tùy chọn 'ảo' cho các phương thức ghi đè trong các lớp dẫn xuất?

class Base { 
    virtual void f(); 
}; 
class Derived : public Base { 
    void f(); // 'virtual' is optional but implied. 
}; 

Câu hỏi của tôi là: Điều gì lý do để thực hiện virtual tùy chọn là gì?

Tôi biết rằng trình biên dịch không phải là điều cần thiết, nhưng tôi nghĩ rằng các nhà phát triển sẽ được hưởng lợi nếu một ràng buộc như vậy được thực thi bởi trình biên dịch.

Ví dụ: đôi khi khi tôi đọc mã của người khác, tôi tự hỏi liệu phương pháp có phải là ảo hay không và tôi phải theo dõi các siêu lớp của nó để xác định điều đó. Và một số tiêu chuẩn mã hóa (Google) làm cho nó trở thành 'phải' để đặt từ khóa virtual vào tất cả các lớp con.

Trả lời

10

Vâng, nó sẽ thực sự đẹp hơn để làm cho trình biên dịch thực thi ảo trong trường hợp này, và tôi đồng ý rằng đây là lỗi trong thiết kế được duy trì cho khả năng tương thích ngược.

Tuy nhiên có một mẹo đó sẽ là bất khả thi mà không có nó:

class NonVirtualBase { 
    void func() {}; 
}; 

class VirtualBase { 
    virtual void func() = 0; 
}; 

template<typename VirtualChoice> 
class CompileTimeVirtualityChoice : public VirtualChoice { 
    void func() {} 
}; 

Với trên chúng ta có thời gian biên dịch lựa chọn thời tiết chúng ta muốn tính ảo của func hay không:

CompileTimeVirtualityChoice<VirtualBase> -- func is virtual 
CompileTimeVirtualityChoice<NonVirtualBase> -- func is not virtual 

... nhưng đã đồng ý, đó là một lợi ích nhỏ cho chi phí tìm kiếm tính năng ảo của một hàm, và bản thân tôi, tôi luôn cố gắng nhập ảo ở mọi nơi nếu có.

+0

Hãy nghĩ rằng bạn muốn xóa '= 0' khỏi' NonVirtualBase' :) –

+0

@Matthieu - tốt bắt! Tôi khá buồn ngủ ngày hôm nay xD –

2

Vì ngôn ngữ không thể thực thi kiểu "tốt", C++ nói chung thậm chí không thử. Ít nhất là IMO, nó mở để đặt câu hỏi liệu có bao gồm các specifier dư thừa như thế này là kiểu tốt trong mọi trường hợp (cá nhân, tôi ghét khi chúng ở đó).

(Ít nhất một phần) Tiêu chuẩn mã hóa của Google có thể có ý nghĩa trong một số trường hợp, nhưng theo như C++ nói chung, thường được coi là lời khuyên tầm thường nhất. Ở một mức độ nào đó, họ thậm chí thừa nhận rằng - một số người trong số họ họ công khai chỉ thực sự ở đó để phù hợp với mã cũ của họ. Các phần khác mà họ không thừa nhận trực tiếp, và (hoàn toàn trung thực) lập luận đó sẽ không hỗ trợ một số tiêu chuẩn của họ (nghĩa là, một số dường như thiếu sự biện minh thực sự).

+0

Nó không phải là ngôn ngữ "không thể", mà là nó chống lại triết lý C++. Một trình biên dịch có thể phát hiện một loạt các vi phạm phong cách, bao gồm cả "ảo" đã được tái sử dụng trong lớp dẫn xuất. –

+0

@Michael: Có lẽ tôi đã nói nó rất tệ, nhưng ý định của tôi là nói rằng nó không thể thực thi phong cách tốt nói chung, vì vậy nó thường không thử, ngay cả trong trường hợp nó rõ ràng có thể). –

3

Điểm yếu trong thiết kế, tôi đồng ý. Tôi cũng nghĩ rằng muốn được thực sự tốt đẹp nếu có một cú pháp khác nhau cho hai điều khác nhau:

  1. Khai báo một hàm ảo. I E. hàm có thể bị ghi đè trong lớp dẫn xuất. (Điều này thực sự thêm mục nhập chức năng mới trong vtable.)
  2. Ghi đè một hàm ảo trong lớp dẫn xuất.

Với quy tắc C++ hiện tại khi ghi đè chức năng - thật dễ dàng để vướng vào mọi thứ. Nếu bạn gõ nhầm tên hàm (hoặc mắc lỗi trong danh sách tham số của nó) - thì bạn thực sự làm (1) thay vì (2).

Và bạn không có lỗi/cảnh báo. Chỉ cần có được sự ngạc nhiên khi chạy.

+1

Một trong những điều mà Object Pascal đã đúng ... –

+1

Đồng ý, tôi nghĩ rằng nó bắt nguồn từ Bjarne Stroustrup và ủy ban mong muốn có một tập hợp từ khóa nhỏ có thể ... do đó tái sử dụng cùng một cho những thứ khác nhau. Trong cùng một mạch, tôi thực sự không đánh giá cao không có một cảnh báo bất cứ khi nào bóng xảy ra: / –

0

Đó là một câu hỏi hay, và tôi chắc chắn đồng ý rằng đó là phong cách tốt để redeclare một phương pháp ảo trong lớp dẫn xuất nếu nó đã được khai báo ảo trong lớp cơ sở. Mặc dù có một số ngôn ngữ xây dựng phong cách thành ngôn ngữ (ví dụ: Google Go và, ở một mức độ nào đó, Python), C++ không phải là một trong những ngôn ngữ đó. Mặc dù trình biên dịch có thể phát hiện ra rằng một lớp dẫn xuất không sử dụng lại từ khóa "ảo" cho một thứ được khai báo "ảo" trong lớp cơ sở (hoặc quan trọng hơn, lớp dẫn xuất khai báo hàm có cùng tên như lớp cơ sở và nó chưa được khai báo ảo trong lớp cơ sở), trên thực tế, các thiết lập trên nhiều trình biên dịch đưa ra các cảnh báo (và thậm chí cả các thông báo lỗi) trong trường hợp điều này xảy ra. Tuy nhiên, ở giai đoạn này, sẽ không thực tế khi yêu cầu một ngôn ngữ như vậy vì có quá nhiều mã không nghiêm ngặt. Hơn nữa, các nhà phát triển luôn có thể chọn nghiêm ngặt hơn ngôn ngữ và có thể bật lên các mức cảnh báo trình biên dịch.

6

Là một lưu ý liên quan, trong C++ 0x bạn có tùy chọn thực thi việc rõ ràng với ghi đè của mình thông qua cú pháp thuộc tính mới.

struct Base { 
    virtual void Virtual(); 
    void NonVirtual(); 
}; 

struct Derived [[base_check]] : Base { 
    //void Virtual(); //Error; didn't specify that you were overriding 
    void Virtual [[override]](); //Not an error 
    //void NonVirtual [[override]](); //Error; not virtual in Base 
    //virtual void SomeRandomFunction [[override]](); //Error, doesn't exist in Base 
}; 

Bạn cũng có thể chỉ định thời điểm bạn định ẩn thành viên qua thuộc tính [[hiding]]. Nó làm cho mã của bạn hơi chi tiết hơn, nhưng nó có thể bắt được nhiều lỗi gây phiền nhiễu tại thời gian biên dịch, như nếu bạn đã làm void Vritual() thay vì void Virtual() và kết thúc giới thiệu một chức năng hoàn toàn mới khi bạn định ghi đè lên một hiện có.