2012-03-03 19 views
11

Tôi đã nghe rất nhiều người nói rằng việc sử dụng typeid là thiết kế tồi tệ, nhưng đối với tôi, có vẻ như nó cung cấp tiện ích tuyệt vời.Tại sao việc sử dụng thiết kế xấu từ khóa typeid?

  1. Khi nào (và tại sao) đang sử dụng typeid "thiết kế kém"?
  2. Khi nào việc sử dụng typeid được chấp nhận?
  3. Khi không chấp nhận được, nhưng bạn vẫn cần một cái gì đó giống nhau, những gì sẽ là một lựa chọn khác mà sẽ có thiết kế tốt?
+7

Người đã nói thiết kế xấu ở đâu? –

+0

@SethCarnegie Tôi đã nghe một số người nói nó trên SO, và cũng đọc nó trong một cuốn sách loại kiểm tra là thiết kế xấu, nhưng không có lý do đã được đưa ra. – xcrypt

+3

Rằng họ không đưa ra lý do có lẽ là một dấu hiệu cho thấy tính hợp pháp của các yêu sách của họ. Một số người nói rằng nếu bạn đang làm rất nhiều loại kiểm tra, bạn nên refactor mã của bạn thành các cuộc gọi chức năng đa hình. Ví dụ, thay vì làm 'if (typeid (a) == atype) doa(); else if (typeid (a) == btype) dob(); else if (typeid (a) == ctype) doc(); 'bạn chỉ có thể làm' a-> doafunc(); 'và hàm ảo thích hợp sẽ được gọi cho kiểu thời gian chạy' a'. –

Trả lời

18

Sự cố không phải với typeid. Vấn đề là nhìn thấy typeid sẽ khuyến khích bạn viết điều này:

PolymorphicType *pType = ...; 
if(typeid(*pType) == typeid(Derived1)) 
    pType->Func1(); 
else if(typeid(*pType) == typeid(Derived2)) 
    pType->Func2(); 
else if(typeid(*pType) == typeid(Derived3)) 
    pType->Func3(); 

Đây là những gì chúng tôi gọi là "thực sự ngu ngốc". Đây là một cuộc gọi hàm ảo được thực hiện theo cách ít hợp lý nhất có thể. typeid có khả năng lạm dụng khi được sử dụng để thay thế các hàm dynamic_castvirtual.

Ví dụ này nghe có vẻ xa vời. Xét cho cùng, rõ ràng đây chỉ là một cuộc gọi ảo. Nhưng mã xấu thường phát triển từ con đường kháng cự ít nhất; tất cả những gì cần làm là để một người làm một số typeid() == typeid() và hạt giống của mã này đã bắt đầu. Nói chung, nếu bạn đang trực tiếp sử dụng typeid thường xuyên, tỷ lệ cược là rất tốt mà bạn đang làm một cái gì đó mà có thể được thực hiện tốt hơn với các cấu trúc ngôn ngữ khác.

typeid là phương pháp khấu trừ loại đa hình của khu nghỉ dưỡng vừa qua.

Mọi việc sử dụng typeid có sai không? Tất nhiên là không. boost::any sẽ không thể có nếu không có nó. Vâng, nó sẽ là có thể, nhưng sẽ không an toàn hơn void*. typeid là những gì làm cho loại an toàn boost::any loại tẩy xóa có thể. Có những cách sử dụng hợp pháp khác của nó.

Nhưng trong tỷ lệ mã vạch để sử dụng, tôi khuyên bạn nên có khoảng một trong 10.000 dòng mã, nhiều nhất. Ít hơn nhiều, và có thể bạn đang sử dụng sai.

Kiểm tra loại có chậm không?

Nói chung, lý do chính để gọi typeid nằm trong mã templated (như trong boost::any) hoặc khi bạn đang mong đợi một loại đa hình. Nếu loại được xác định tĩnh (ví dụ: một tên tệp hoặc giá trị của một loại không đa hình), thì bạn có thể mong đợi nó được thực hiện tại thời gian biên dịch.

Đó là các giá trị đa hình mà bạn nên quan tâm. Tôi đã nhìn thấy một thử nghiệm hiệu suất cho thấy rằng một số triển khai thực hiện typeid thực hiện việc phân cấp lớp, do đó, thời gian cần để tìm loại là tỷ lệ thuận với số lớp giữa loại thực và loại đã cho. Mọi triển khai sẽ khác nhau, nhưng đó là một dấu hiệu khá tốt có thể bạn không nên đặt nó vào mã hiệu suất quan trọng.

+2

'typeid (x) .name()' cũng khá tiện dụng để viết các câu lệnh debugging/log. –

+0

Bạn có thể cung cấp một liên kết đến nơi 'boost :: any' sử dụng' typeid' không? –

+0

@BenVoigt: Tôi không biết mã nguồn của 'boost :: any' ở đâu, nhưng dòng 180 của' boost/any.hpp' trong phiên bản 1.47 có nó. Thật vậy, chỉ cần tìm kiếm tập tin đó sẽ hiển thị một số sử dụng của nó. –

6

Tôi không nghĩ có ai nói rằng việc sử dụng typeid chính là thiết kế kém; thay vào đó, điều bạn sẽ nghe thấy là việc sử dụng typeidchỉ định thiết kế kém. Ý tưởng có bất kỳ logic nào phân biệt (nói) Square từ Circle thực sự phải là trong các lớp học, do đó phải được thể hiện bằng chức năng ảo (đa hình).

Không cần phải nói, đây không phải là quy tắc tuyệt đối.

+0

Và hiệu suất khôn ngoan, có bất kỳ lý do? Kiểm tra kiểu có chậm không? – xcrypt

+3

@xcrypt trừ khi toán hạng của 'typeid' là một kiểu đa hình,' typeid' được thực hiện tại thời gian biên dịch. Nếu toán hạng là một kiểu đa hình, tốc độ hay chậm của nó phụ thuộc vào cách các trình biên dịch biên dịch thực hiện nó như thế nào. Nếu họ thực hiện nó bằng cách làm cho chương trình gửi văn bản của chương trình của bạn cho một nhân viên công ty và có anh ta gõ loại của lớp trở lại chương trình, sau đó nó sẽ được làm chậm. –

+0

@SethCarnegie, người sẽ làm một việc như vậy? : p – xcrypt

4

Thật tệ khi những người đã cho bạn lời khuyên tốt này không thể đưa ra lý do. Nó có nghĩa là họ đang lập trình đơn giản cargo cult.

Tất cả các biểu thức đều có loại thời gian biên dịch tĩnh (sau khi mở rộng mẫu). Tất cả các loại đều đa hình hay không.

tôi sẽ cố gắng xây dựng cho bạn biết tại sao typeid là (điều gần gũi nhất với) vô dụng trong cả hai trường hợp:

  1. Không có lý do để sử dụng typeid khi loại tĩnh của biểu thức là không đa hình. Trình biên dịch sẽ sử dụng kiểu tĩnh, không phải kiểu động, do đó, việc tính toán quá tải hàm thời gian hoạt động. Và loại danh tính có thể được kiểm tra hiệu quả hơn nhiều bằng cách sử dụng một đặc điểm kiểu templated như is_same.

  2. Nếu loại tĩnh của biểu thức là đa hình, thì typeid không thành công. Có, nó sẽ cung cấp thông tin về loại động của đối tượng. Nhưng chỉ loại năng động có nguồn gốc cao nhất. Điều này vi phạm Nguyên tắc thay thế Liskov, vì các loại được cho là BaseChild, thêm loại mới Grandchild có nguồn gốc từ Child sẽ không được coi là Child. Nguyên tắc Open-Closed cũng bị vi phạm, vì thiết kế không thể được mở rộng với các kiểu mới mà không cần viết lại mọi thứ. Không có đóng gói, vì thông tin về hệ thống phân cấp lớp phải nằm rải rác trong suốt chương trình, tạo ra sự ghép nối mạnh mẽ. dynamic_cast khắc phục một số nhưng không phải tất cả những vấn đề này.

Tóm lại, một chương trình sử dụng typeid sẽ rất khó duy trì và kiểm tra.

Và, như với bất kỳ quy tắc nào, có thể có ngoại lệ hạn chế. Nơi bạn đang sử dụng các tính năng ngôn ngữ OO của các hàm kế thừa và ảo, nhưng bạn không thực sự muốn có một thiết kế đa hình. Tôi có thể nghĩ chính xác một: nơi bạn muốn đăng nhập tên của loại như một chuỗi, sử dụng type_info::name(). Bạn cũng có thể sử dụng nó để phát hiện cắt (nơi đa hình đã bị vi phạm). Nhưng điều này là tốt để gỡ lỗi chỉ, không bao giờ để kiểm soát dòng chảy chương trình.

+1

"Không có lý do gì để sử dụng typeid khi loại tĩnh của biểu thức không đa hình." Trừ khi bạn đang viết 'boost :: any', và bạn muốn cung cấp sự bảo đảm cho người dùng rằng kiểu tĩnh họ đẩy vào biến là cùng một cái mà họ đang cố gắng loại bỏ nó. Bạn biết đấy, 99% điểm của 'boost :: any'. –

+0

@Nicol: IMO, đó là một trong những cách sử dụng gỡ lỗi và 'any_cast' thậm chí không xử lý các loại chính xác để bắt đầu. Tất cả các vấn đề với 'typeid' chuyển sang' boost :: any', và kết quả là các chương trình sử dụng 'boost :: any' rất khó để duy trì và kiểm tra. –

+0

Điều đó không có ý nghĩa. 'boost :: any' là một loại' void * 'an toàn kiểu. Đó là * công việc * là để đảm bảo rằng người đưa thứ gì đó vào 'bất kỳ' và người lấy thứ gì đó ra khỏi' bất kỳ' đồng ý với cái gì đó, để không vi phạm C++ và gây ra hành vi không xác định. Không có gì "khó duy trì và kiểm tra" về điều này; những gì bạn đưa vào là những gì bạn nhận ra. "Thay thế Liskov" và "Open-Closed" là * không liên quan * trong ngữ cảnh này. Tất nhiên có khớp nối mạnh mẽ, nhưng * đó là những gì bạn yêu cầu * khi bạn quyết định sử dụng loại tẩy xoá. Ít nhất nó là * an toàn * khớp nối mạnh mẽ. –

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