Các trình hủy ảo có được kế thừa không?


77

Nếu tôi có một lớp cơ sở với một hàm hủy ảo. Có một lớp dẫn xuất để khai báo một hàm hủy ảo không?

class base {
public:
    virtual ~base () {}
};

class derived : base {
public:
    virtual ~derived () {} // 1)
    ~derived () {}  // 2)
};

Câu hỏi cụ thể:

  1. 1) và 2) có giống nhau không? 2) tự động ảo là do cơ địa của nó hay do nó "dừng" cái ảo?
  2. Có thể bỏ qua hàm hủy dẫn xuất nếu nó không có gì để làm không?
  3. Cách tốt nhất để khai báo hàm hủy dẫn xuất là gì? Khai báo nó ảo, không ảo hoặc bỏ qua nó nếu có thể?

Câu trả lời:


92
  1. Vâng, họ giống nhau. Lớp dẫn xuất không khai báo một cái gì đó ảo không ngăn nó trở thành ảo. Trên thực tế, không có cách nào để ngăn bất kỳ phương thức nào (bao gồm hàm hủy) trở thành ảo trong một lớp dẫn xuất nếu nó là ảo trong một lớp cơ sở. Trong> = C ++ 11, bạn có thể sử dụng finalđể ngăn nó bị ghi đè trong các lớp dẫn xuất, nhưng điều đó không ngăn nó bị ảo.
  2. Có, một hàm hủy trong một lớp dẫn xuất có thể bị bỏ qua nếu nó không có gì để làm. Và nó có ảo hay không không quan trọng.
  3. Tôi sẽ bỏ qua nó nếu có thể. Và tôi luôn sử dụng lại virtualtừ khóa cho các hàm ảo trong các lớp dẫn xuất vì lý do rõ ràng. Mọi người không cần phải đi lên toàn bộ hệ thống phân cấp kế thừa để tìm ra rằng một hàm là ảo. Ngoài ra, nếu lớp của bạn có thể sao chép hoặc di chuyển được mà không cần phải khai báo bản sao hoặc di chuyển các hàm tạo của riêng bạn, thì việc khai báo hàm hủy thuộc bất kỳ loại nào (ngay cả khi bạn định nghĩa nó là default) sẽ buộc bạn phải khai báo bản sao và di chuyển các hàm tạo và toán tử gán nếu bạn muốn chúng vì trình biên dịch sẽ không đưa chúng vào cho bạn nữa.

Như một điểm nhỏ cho mục 3. Nó đã được chỉ ra trong các nhận xét rằng nếu một trình hủy không được khai báo, trình biên dịch sẽ tạo ra một giá trị mặc định (vẫn là ảo). Và mặc định đó là một hàm nội tuyến.

Các hàm nội tuyến có khả năng khiến chương trình của bạn tiếp xúc với các thay đổi trong các phần khác của chương trình và làm cho khả năng tương thích nhị phân đối với các thư viện dùng chung trở nên khó khăn. Ngoài ra, việc gia tăng khớp nối có thể dẫn đến việc biên dịch lại nhiều khi đối mặt với một số loại thay đổi. Ví dụ: nếu bạn quyết định rằng bạn thực sự muốn triển khai cho trình hủy ảo của mình thì mọi đoạn mã gọi nó sẽ cần được biên dịch lại. Trong khi nếu bạn đã khai báo nó trong phần thân lớp và sau đó xác định nó trống trong một .cpptệp, bạn sẽ ổn khi thay đổi nó mà không cần biên dịch lại.

Lựa chọn cá nhân của tôi vẫn là bỏ qua nó khi có thể. Theo ý kiến ​​của tôi, nó làm lộn xộn mã và trình biên dịch đôi khi có thể làm những việc hiệu quả hơn một chút với việc triển khai mặc định trên một bản trống. Nhưng có những ràng buộc mà bạn có thể gặp phải khiến đó là một lựa chọn tồi.


1
Tôi không đồng ý với phần 'bỏ qua'. Không tốn nhiều chi phí để khai báo nó trong tiêu đề và định nghĩa nó (nội dung trống) trong nguồn. Nếu bạn làm như vậy, bạn luôn có thể quay lại và thêm một số bước (ghi nhật ký?) Mà không buộc khách hàng của bạn phải biên dịch lại.
Matthieu M.

1
Trên thực tế, tôi không khai báo nhiều hàm nội tuyến, thậm chí không phải là 'trình truy cập' cổ điển, nhưng sau đó làm việc trong một công ty lớn, chúng ta có thể có các ràng buộc tương thích nhị phân cao hơn hầu hết.
Matthieu M.

3
Tôi vừa học được từ buổi nói chuyện này rằng việc khai báo hàm hủy ảo sẽ thực sự khiến lớp của bạn trở nên không thể di chuyển được! Vì vậy, bất cứ khi nào bạn khai báo một hàm hủy ảo, bạn cũng phải cung cấp toàn bộ quy tắc 5 nếu bạn muốn những thuộc tính đó. Thậm chí nhiều lý do hơn để bỏ qua khi có thể.
Neil Traft

1
"Ngoài ra, nếu lớp của bạn có thể sao chép hoặc di chuyển được mà không cần phải khai báo bản sao hoặc di chuyển các hàm tạo của riêng bạn, việc khai báo một hàm hủy thuộc bất kỳ loại nào (ngay cả khi bạn định nghĩa nó là mặc định) sẽ buộc bạn phải khai báo bản sao và di chuyển các hàm tạo và toán tử gán nếu bạn muốn chúng vì trình biên dịch sẽ không đưa chúng vào cho bạn nữa. " Đó là sai lầm! vi.cppreference.com/w/cpp/language/copy_constructor
Kaiserludi

1
@Kaiserludi - Tôi sẽ kiểm tra lại xem điều này có đúng không và sửa câu trả lời của mình.
Omnifarious

2
  1. Hàm hủy tự động là ảo, như với tất cả các phương thức. Bạn không thể ngăn một phương thức trở thành ảo trong C ++ (nếu nó đã được khai báo là ảo, tức là không có tương đương với 'cuối cùng' trong Java)
  2. Có nó có thể được bỏ qua.
  3. Tôi sẽ khai báo một hàm hủy ảo nếu tôi định cho lớp này được phân lớp, bất kể nó có phân lớp con khác hay không, tôi cũng muốn tiếp tục khai báo các phương thức ảo, mặc dù nó không cần thiết. Điều này sẽ giữ cho các lớp con hoạt động, nếu bạn quyết định xóa phần thừa kế. Nhưng tôi cho rằng đây chỉ là vấn đề về phong cách.

Bộ hủy không tự động ảo và cũng không phải là bất kỳ chức năng thành viên nào khác.

1
@Neil; tất nhiên là không, tôi đang đề cập đến hàm hủy trong ví dụ (tức là trong đó lớp cơ sở có một lớp ảo), không phải hàm hủy nói chung. Và điều này đúng cho tất cả các phương thức, không chỉ hàm hủy.
falstro

1
Kể từ C ++ 11, chúng tôi có final.
whoan 22/09/18

1

Một hàm thành viên ảo sẽ làm cho mọi sự quá tải của hàm này trở thành ảo.

Vì vậy, ảo trong 1) là "tùy chọn", hàm hủy lớp cơ sở là ảo làm cho tất cả các hàm hủy con cũng ảo.


0

1 / Có 2 / Có, nó sẽ được tạo bởi trình biên dịch 3 / Lựa chọn giữa việc khai báo nó là ảo hay không phải tuân theo quy ước của bạn về việc ghi đè các thành viên ảo - IMHO, có những đối số tốt cả hai cách, chỉ cần chọn một và làm theo nó.

Tôi sẽ bỏ qua nó nếu có thể, nhưng có một điều có thể thúc giục bạn khai báo nó: nếu bạn sử dụng trình biên dịch tạo ra, nó hoàn toàn là nội tuyến. Đôi khi bạn muốn tránh các thành viên nội tuyến (thư viện động chẳng hạn).


0

Các hàm ảo được ghi đè ngầm. Khi phương thức của một lớp con khớp với chữ ký phương thức của hàm ảo từ một lớp cơ sở, nó sẽ bị ghi đè. Điều này rất dễ nhầm lẫn và có thể bị hỏng trong quá trình tái cấu trúc, vì vậy có overridefinaltừ khóa kể từ C ++ 11 để đánh dấu hành vi này một cách rõ ràng. Ví dụ: có một cảnh báo tương ứng cấm hành vi im lặng-Wsuggest-override trong GCC.

Có một câu hỏi liên quan cho overridefinaltừ khóa trên SO: Từ khóa 'ghi đè' chỉ là một kiểm tra cho một phương pháp ảo bị ghi đè?.

Và tài liệu trong cpp tham khảo https://en.cppreference.com/w/cpp/language/override

Có nên sử dụng overridetừ khóa với hàm hủy hay không vẫn còn là một vấn đề tranh luận. Ví dụ, xem thảo luận trong câu hỏi SO liên quan này: ghi đè mặc định của bộ hủy ảo Vấn đề là, ngữ nghĩa của bộ hủy ảo khác với các chức năng bình thường. Các hàm hủy được xâu chuỗi, vì vậy tất cả các hàm hủy của các lớp cơ sở được gọi sau khi con một. Tuy nhiên, trong trường hợp một cơ sở phương thức thông thường, việc triển khai phương thức được ghi đè không được gọi theo mặc định. Chúng có thể được gọi theo cách thủ công khi cần thiết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.