Một vtable
gì
Có thể hữu ích khi biết thông báo lỗi đang nói về điều gì trước khi cố gắng sửa nó. Tôi sẽ bắt đầu ở cấp độ cao, sau đó tìm hiểu thêm một số chi tiết. Bằng cách đó mọi người có thể bỏ qua phía trước một khi họ cảm thấy thoải mái với sự hiểu biết của họ về vtables. Sôi và có một đám người bỏ qua ngay bây giờ. :) Đối với những người gắn bó xung quanh:
Một vtable về cơ bản là triển khai đa hình phổ biến nhất trong C ++ . Khi vtables được sử dụng, mọi lớp đa hình đều có vtable ở đâu đó trong chương trình; bạn có thể nghĩ về nó như một static
thành viên dữ liệu (ẩn) của lớp. Mỗi đối tượng của một lớp đa hình được liên kết với vtable cho lớp có nguồn gốc nhất của nó. Bằng cách kiểm tra sự liên kết này, chương trình có thể hoạt động phép thuật đa hình của nó. Thông báo quan trọng: một vtable là một chi tiết thực hiện. Nó không bắt buộc theo tiêu chuẩn C ++, mặc dù hầu hết (tất cả?) Trình biên dịch C ++ sử dụng vtables để thực hiện hành vi đa hình. Các chi tiết tôi đang trình bày là cách tiếp cận điển hình hoặc hợp lý. Trình biên dịch được phép đi chệch khỏi điều này!
Mỗi đối tượng đa hình có một con trỏ (ẩn) đến vtable cho lớp có nguồn gốc nhất của đối tượng (có thể là nhiều con trỏ, trong các trường hợp phức tạp hơn). Bằng cách nhìn vào con trỏ, chương trình có thể cho biết loại "thực" của một đối tượng là gì (ngoại trừ trong quá trình xây dựng, nhưng hãy bỏ qua trường hợp đặc biệt đó). Ví dụ, nếu một đối tượng kiểu A
không trỏ đến vtable của A
, thì đối tượng đó thực sự là một đối tượng phụ của một cái gì đó có nguồn gốc từ A
.
Cái tên "vtable" xuất phát từ " v chức năng irtual bảng ". Đó là một bảng lưu trữ các con trỏ tới các hàm (ảo). Một trình biên dịch chọn quy ước của nó về cách đặt bảng; một cách tiếp cận đơn giản là đi qua các hàm ảo theo thứ tự chúng được khai báo trong các định nghĩa lớp. Khi một hàm ảo được gọi, chương trình theo con trỏ của đối tượng đến vtable, đi đến mục được liên kết với hàm mong muốn, sau đó sử dụng con trỏ hàm được lưu trữ để gọi đúng hàm. Có nhiều thủ thuật khác nhau để thực hiện công việc này, nhưng tôi sẽ không đi sâu vào những vấn đề ở đây.
Ở đâu / khi nào được vtable
tạo ra?
Trình biên dịch vtable được tạo tự động (đôi khi được gọi là "phát ra") bởi trình biên dịch. Một trình biên dịch có thể phát ra một vtable trong mọi đơn vị dịch thuật nhìn thấy định nghĩa lớp đa hình, nhưng điều đó thường sẽ là quá mức cần thiết. Một cách khác ( được sử dụng bởi gcc và có thể bởi những người khác) là chọn một đơn vị dịch thuật để đặt vtable, tương tự như cách bạn sẽ chọn một tệp nguồn duy nhất để đặt các thành viên dữ liệu tĩnh của lớp. Nếu quá trình lựa chọn này không chọn được bất kỳ đơn vị dịch thuật nào, thì vtable trở thành một tham chiếu không xác định. Do đó, lỗi, thông báo được thừa nhận là không đặc biệt rõ ràng.
Tương tự, nếu quá trình lựa chọn không chọn một đơn vị dịch, nhưng tệp đối tượng đó không được cung cấp cho trình liên kết, thì vtable trở thành một tham chiếu không xác định. Thật không may, thông báo lỗi có thể thậm chí còn ít rõ ràng hơn trong trường hợp này so với trường hợp quá trình lựa chọn thất bại. (Cảm ơn những người trả lời đã đề cập đến khả năng này. Tôi có lẽ đã quên nó nếu không.)
Quá trình lựa chọn được sử dụng bởi gcc có ý nghĩa nếu chúng ta bắt đầu với truyền thống dành một tệp nguồn (duy nhất) cho mỗi lớp cần một để thực hiện. Sẽ thật tuyệt khi phát ra vtable khi biên dịch tệp nguồn đó. Hãy gọi đó là mục tiêu của chúng tôi. Tuy nhiên, quá trình lựa chọn cần phải hoạt động ngay cả khi truyền thống này không được tuân theo. Vì vậy, thay vì tìm kiếm việc thực hiện toàn bộ lớp, chúng ta hãy tìm kiếm việc thực hiện một thành viên cụ thể của lớp. Nếu truyền thống được tiếp nối - và nếu thành viên đó thực tế được thực hiện - thì điều này đạt được mục tiêu.
Thành viên được chọn bởi gcc (và có khả năng bởi các trình biên dịch khác) là hàm ảo không nội tuyến đầu tiên không phải là ảo thuần túy. Nếu bạn là một phần của đám đông tuyên bố các hàm tạo và hàm hủy trước các hàm thành viên khác, thì hàm hủy đó có cơ hội được chọn. (Bạn đã nhớ làm cho hàm hủy ảo, phải không?) Có các trường hợp ngoại lệ; Tôi hy vọng rằng các ngoại lệ phổ biến nhất là khi định nghĩa nội tuyến được cung cấp cho hàm hủy và khi yêu cầu hủy mặc định (sử dụng " = default
").
Người thông minh có thể nhận thấy rằng một lớp đa hình được phép cung cấp các định nghĩa nội tuyến cho tất cả các chức năng ảo của nó. Điều đó không làm cho quá trình lựa chọn thất bại? Nó không có trong trình biên dịch cũ. Tôi đã đọc rằng các trình biên dịch mới nhất đã giải quyết tình huống này, nhưng tôi không biết số phiên bản có liên quan. Tôi có thể thử tìm kiếm cái này, nhưng nó dễ dàng hơn để viết mã xung quanh nó hoặc đợi trình biên dịch phàn nàn.
Tóm lại, có ba nguyên nhân chính gây ra lỗi "không xác định tham chiếu đến vtable":
- Một hàm thành viên bị thiếu định nghĩa của nó.
- Một tệp đối tượng không được liên kết.
- Tất cả các chức năng ảo có định nghĩa nội tuyến.
Những nguyên nhân này tự chúng không đủ để gây ra lỗi. Thay vào đó, đây là những gì bạn sẽ giải quyết để khắc phục lỗi. Đừng hy vọng rằng việc cố tình tạo một trong những tình huống này chắc chắn sẽ tạo ra lỗi này; Có những yêu cầu khác. Đừng hy vọng rằng việc giải quyết các tình huống này sẽ giải quyết lỗi này.
(OK, số 3 có thể đã đủ khi câu hỏi này được hỏi.)
Làm thế nào để sửa lỗi?
Chào mừng trở lại mọi người bỏ qua phía trước! :)
- Nhìn vào định nghĩa lớp học của bạn. Tìm hàm ảo không nội tuyến đầu tiên không phải là thuần ảo (không phải "
= 0
") và định nghĩa bạn cung cấp (không phải " = default
").
- Nếu không có chức năng như vậy, hãy thử sửa đổi lớp của bạn để có một cái. (Lỗi có thể được giải quyết.)
- Xem thêm câu trả lời của Philip Thomas cho một cảnh báo.
- Tìm định nghĩa cho hàm đó. Nếu nó bị thiếu, thêm nó! (Lỗi có thể được giải quyết.)
- Kiểm tra lệnh liên kết của bạn. Nếu nó không đề cập đến tệp đối tượng với định nghĩa của hàm đó, hãy sửa nó! (Lỗi có thể được giải quyết.)
- Lặp lại các bước 2 và 3 cho từng chức năng ảo, sau đó cho từng chức năng không ảo, cho đến khi lỗi được giải quyết. Nếu bạn vẫn bị kẹt, lặp lại cho từng thành viên dữ liệu tĩnh.
Ví dụ
Chi tiết về những việc cần làm có thể khác nhau và đôi khi phân nhánh thành các câu hỏi riêng biệt (như Tham chiếu không xác định / lỗi biểu tượng bên ngoài chưa được giải quyết là gì và làm cách nào để khắc phục? ). Tuy nhiên, tôi sẽ cung cấp một ví dụ về những việc cần làm trong một trường hợp cụ thể có thể làm rối loạn các lập trình viên mới hơn.
Bước 1 đề cập đến việc sửa đổi lớp của bạn để nó có chức năng của một loại nhất định. Nếu mô tả về chức năng đó đi qua đầu bạn, bạn có thể đang ở trong tình huống tôi dự định giải quyết. Hãy nhớ rằng đây là một cách để hoàn thành mục tiêu; đó không phải là cách duy nhất và dễ dàng có thể có những cách tốt hơn trong tình huống cụ thể của bạn. Hãy gọi lớp của bạn A
. Là hàm hủy của bạn được khai báo (theo định nghĩa lớp của bạn) là một trong hai
virtual ~A() = default;
hoặc là
virtual ~A() {}
? Nếu vậy, hai bước sẽ thay đổi hàm hủy của bạn thành loại hàm chúng ta muốn. Đầu tiên, thay đổi dòng đó thành
virtual ~A();
Thứ hai, đặt dòng sau vào tệp nguồn là một phần của dự án của bạn (tốt nhất là tệp có triển khai lớp, nếu bạn có):
A::~A() {}
Điều đó làm cho hàm hủy (ảo) của bạn không nội tuyến và không được tạo bởi trình biên dịch. (Hãy thoải mái sửa đổi mọi thứ để phù hợp hơn với phong cách định dạng mã của bạn, chẳng hạn như thêm nhận xét tiêu đề vào định nghĩa hàm.)