2011-01-06 18 views
7

http://en.wikipedia.org/wiki/TypeidCách hoạt động của Typeid và các đối tượng lưu trữ thông tin lớp như thế nào?

Điều này có vẻ là một điều bí ẩn đối với tôi: trình biên dịch lưu trữ thông tin về loại đối tượng như thế nào? Về cơ bản một lớp trống, một khi đã khởi tạo, không có kích thước bằng không trong bộ nhớ.

+1

Đối tượng C++ không bao giờ có kích thước bằng 0 (ngoại trừ đối tượng phụ lớp cơ sở đó có thể). Tuy nhiên, điều này không phải vì 'typeid', bởi vì nếu bạn tạo ra một mảng có thứ gì đó bằng 0, tất cả các đối tượng sẽ ở cùng một địa chỉ. Nó hữu ích như là một tài sản của ngôn ngữ mà các đối tượng riêng biệt từng có địa chỉ riêng của họ, do đó không có đối tượng kích thước bằng không. Điều này không quan trọng lắm đối với các đối tượng phụ lớp cơ sở, nếu chỉ vì mọi cái được sử dụng cho ý tưởng rằng một số lớp cơ sở của cùng một đối tượng có thể ở cùng một địa chỉ - đó là điều thường xảy ra trong thừa kế đơn. –

Trả lời

9

Cách thức lưu trữ được xác định thực hiện. Có nhiều cách hoàn toàn khác nhau để làm điều đó.

Tuy nhiên, đối với các loại không đa hình không có gì cần lưu trữ. Đối với các loại không đa hình typeid trả về thông tin về loại tĩnh của biểu thức, nghĩa là loại thời gian biên dịch. Loại này luôn được biết ở thời gian biên dịch, vì vậy không cần liên kết bất kỳ thông tin bổ sung nào với các đối tượng cụ thể (giống như đối với sizeof để hoạt động, bạn không thực sự cần lưu trữ kích thước đối tượng ở bất kỳ đâu). "Một đối tượng trống" mà bạn đề cập trong câu hỏi của bạn sẽ là đối tượng của loại không đa hình, do đó không cần lưu trữ bất kỳ thứ gì trong đó và không có vấn đề gì với nó có kích thước bằng không. (Trong khi đó, các đối tượng đa hình không bao giờ thực sự "trống rỗng" và không bao giờ có "zero kích thước trong bộ nhớ".)

Đối với các loại đa hình typeid không thực sự trả lại thông tin về động kiểu của biểu thức, tức là khoảng chạy của nó -time loại. Để thực hiện điều này, một cái gì đó phải được lưu trữ bên trong đối tượng thực tế tại thời gian chạy. Như tôi đã nói ở trên, các trình biên dịch khác nhau thực hiện nó một cách khác nhau. Trong MSVC++, một ví dụ, con trỏ VMT được lưu trữ trong mỗi điểm đối tượng đa hình thành một cấu trúc dữ liệu có chứa thông tin kiểu RTTI-run-time-thời gian về đối tượng - ngoài VMT thực tế.

Thực tế là bạn đề cập đến các đối tượng có kích thước bằng không trong câu hỏi của bạn có thể chỉ ra rằng bạn có một số quan niệm sai về những gì typeid có thể và không thể thực hiện được. Hãy nhớ rằng, một lần nữa, typeid có khả năng xác định loại thực tế (nghĩa là động) của đối tượng cho các loại đa hình chỉ. Đối với các loại không đa hình typeid không thể xác định loại thực tế của đối tượng và hoàn nguyên về chức năng biên dịch nguyên thủy.

+0

đó là lý do số 1 mọi người đã rên rỉ về C++, bởi vì đó là một sự mất mát nhẹ về kiểm soát bộ nhớ. Trong khi tôi đang lấy một thời gian ngắn ở phía bên của lập trình viên kiểm soát-freak, làm thế nào là loại kiểm tra thực sự được thực hiện, là nó một số nguyên? Tôi có thể xem triển khai thực tế ở đâu? – jokoon

+0

@gokoon: Nếu trình biên dịch của bạn không ghi lại nó, bạn sẽ cần phải đảo ngược kỹ sư từ mã được tạo (ví dụ: biên dịch thành mã asm). –

+0

@gokoon: Bạn có thể thấy việc triển khai thực tế trong các trình biên dịch mã nguồn mở/miễn phí C++, và có một số trong đó. Không phải tất cả các trình biên dịch phải làm những việc theo cùng một cách, vì vậy nếu bạn quan tâm đến một trình biên dịch cụ thể, như Visual C++, bạn sẽ phải nghiên cứu một cách cụ thể. –

1

Ngay cả khi bạn không sử dụng thông tin loại, một lớp trống sẽ không có byte không, nó luôn luôn có một cái gì đó, nếu tôi nhớ chính xác nhu cầu tiêu chuẩn đó.

Tôi tin rằng typeid được triển khai tương tự như con trỏ vtable, đối tượng sẽ có con trỏ "ẩn" đối với typeid của nó.

+2

Không cần phải thêm vào mọi cá thể để hỗ trợ typeid: bạn chỉ có thể sử dụng một mục nhập vtable, vì mỗi cá thể đã trỏ đến một vtable kiểu động cụ thể. Đây là lý do tại sao bạn chỉ có thể sử dụng typeid để có được kiểu động của một đối tượng nếu nó có ít nhất một phương thức ảo. –

+0

Thats cũng có thể. – bcsanches

0

Có một số câu hỏi trong một câu hỏi của bạn.

Trong đối tượng C++ là thứ chiếm bộ nhớ. Nếu nó không chiếm bất kỳ bộ nhớ nào - nó không phải là một đối tượng (mặc dù lớp con của đối tượng cơ sở có thể chiếm không gian). Vì vậy, một đối tượng phải chiếm ít nhất 1 byte.

Trình biên dịch không lưu trữ bất kỳ thông tin loại nào trừ khi lớp của bạn có chức năng ảo. Trong trường hợp đó, một con trỏ để nhập thông tin thường được lưu trữ ở mức chênh lệch âm trong bảng chức năng ảo. Lưu ý rằng tiêu chuẩn không đề cập đến bất kỳ bảng ảo hoặc định dạng thông tin kiểu nào để nó hoàn toàn là một chi tiết thực hiện.

6

Hãy tưởng tượng mỗi lớp như thể nó có phương pháp ảo này, nhưng chỉ khi nó đã có một ảo, và một đối tượng khác được tạo ra cho mỗi loại:

extern std::type_info __Example_info; 
struct Example { 
    virtual std::type_info const& __typeid() const { 
    return __Example_info; 
    } 
}; 
// "__" used to create reserved names in this pseudo-implementation 

Sau đó tưởng tượng bất kỳ sử dụng typeid trên một đối tượng, typeid(obj), trở thành obj.__typeid(). Sử dụng trên con trỏ tương tự trở thành pointer->__typeid(). Ngoại trừ sử dụng trên con trỏ null (mà ném bad_typeid), trường hợp con trỏ là giống hệt với trường hợp không phải con trỏ sau khi dereferencing, và tôi sẽ không đề cập đến nó thêm nữa. Khi áp dụng trực tiếp trên một loại, hãy tưởng tượng rằng trình biên dịch chèn một tham chiếu trực tiếp vào đối tượng được yêu cầu: typeid(Example) trở thành __Example_info.

Nếu một lớp không có RTTI (tức là nó không có virtuals, ví dụ như NoRTTI bên dưới), sau đó tưởng tượng nó với một giống __typeid phương pháp đó là không ảo. Điều này cho phép chuyển đổi tương tự thành các cuộc gọi phương thức như trên, dựa vào công văn ảo hoặc phi ảo của các phương thức đó, nếu thích hợp; nó cũng cho phép một số lời gọi phương thức ảo được chuyển thành công văn không ảo, vì có thể được thực hiện cho bất kỳ phương thức ảo nào.

struct NoRTTI {}; // a hierarchy can mix RTTI and no-RTTI, just as use of 
        // virtual methods can be in a derived class even if the base 
        // doesn't contain any 
struct A : NoRTTI { virtual ~A(); }; // one virtual required for RTTI 
struct B : A {}; // ~B is virtual through inheritance 

void typeid_with_rtti(A &a, B &b) { 
    typeid(a); typeid(b); 
    A local_a; // no RTTI required: typeid(local_a); 
    B local_b; // no RTTI required: typeid(local_b); 

    A &ref = local_b; 
    // no RTTI required, if the compiler is smart enough: typeid(ref) 
} 

Ở đây, typeid phải sử dụng RTTI cho cả hai tham số (B có thể là một lớp cơ sở cho một loại mới hơn), nhưng không cần RTTI cho một trong hai biến cục bộ vì kiểu động (hoặc "loại runtime") hoàn toàn được biết đến. Điều này phù hợp, không phải ngẫu nhiên, làm thế nào các cuộc gọi ảo có thể tránh công văn ảo.

struct StillNoRTTI : NoRTTI {}; 

void typeid_without_rtti(NoRTTI &obj) { 
    typeid(obj); 
    StillNoRTTI derived; typeid(derived); 
    NoRTTI &ref = derived; typeid(ref); 

    // typeid on types never uses RTTI: 
    typeid(A); typeid(B); typeid(NoRTTI); typeid(StillNoRTTI); 
} 

Ở đây, sử dụng ở hai obj hoặc ref sẽ tương ứng với NoRTTI! này đúng ngay cả mặc dù trước đây có thể của một lớp dẫn xuất (obj có thể thực sự là một thể hiện của Một hoặc B) và mặc dù ref chắc chắn của một lớp được thừa kế là. Tất cả các ứng dụng khác (dòng cuối cùng của hàm) cũng sẽ được giải quyết tĩnh.

Lưu ý rằng trong các hàm mẫu này, mỗi loại typeid sử dụng RTTI hoặc không phải như tên hàm ngụ ý. (Do đó việc sử dụng nhận xét trong with_rtti.)

+0

Tôi sẽ cần phải đọc nhiều hơn một lần nữa. Tôi không đặt câu trả lời này là câu trả lời được chấp nhận vì nó có vẻ khá chi tiết, nhưng câu trả lời dưới đây khá giải thích hơn là sử dụng các ví dụ tàn bạo. – jokoon

+0

@gokoon: Ah, bạn đã có nó như là câu trả lời được chấp nhận trong giây lát ...:) Tôi thấy hiệu quả nhất khi dệt giải thích bằng các ví dụ thực tế, đặc biệt là khi bạn có thể thử chúng (ví dụ: bằng cách áp dụng kiểu chuyển đổi typeid (obj) sang obj .__ typeid() theo cách thủ công), nhưng các nét khác nhau. (Tôi đã chỉ cập nhật cố gắng tập trung vào sự rõ ràng.) –

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