2009-02-24 30 views
8

Gần đây tôi đã làm việc trên một đoạn mã C++ cho một dự án phụ (cpp-markdown library, cho tò mò), và chạy vào một câu hỏi mã hóa mà tôi muốn một số ý kiến.Tránh dynamic_cast/RTTI

cpp-markdown có một lớp cơ sở được gọi là Token, có một số lớp con. Hai trong số các lớp con chính là Container (chứa các bộ sưu tập khác Token s) và TextHolder (được sử dụng làm lớp cơ sở cho Token s có chứa văn bản, tất nhiên).

Hầu hết quá trình xử lý được xử lý qua chức năng ảo, nhưng một số xử lý được xử lý tốt hơn trong một hàm duy nhất. Đối với điều đó, tôi đã sử dụng dynamic_cast để giảm con trỏ từ một số Token* sang một trong các lớp con của nó, vì vậy tôi có thể gọi các hàm dành riêng cho lớp con và các lớp con của nó. Không có cơ hội đúc sẽ thất bại, bởi vì mã có thể cho biết khi một điều như vậy là cần thiết thông qua các chức năng ảo (chẳng hạn như isUnmatchedOpenMarker).

Có hai cách khác tôi có thể thấy để xử lý này:

  1. Tạo tất cả trong những chức năng mà tôi muốn gọi là chức năng ảo của Token, và chỉ để lại cho họ một cơ thể trống cho mỗi phân lớp ngoại trừ một (các) phân loại cần xử lý chúng, hoặc ...

  2. Tạo hàm ảo trong Token sẽ trả về con trỏ được nhập đúng cách this khi được gọi trên một số kiểu con nhất định và con trỏ rỗng nếu được gọi trên bất cứ thứ gì khác. Về cơ bản một phần mở rộng của hệ thống chức năng ảo mà tôi đã sử dụng ở đó.

Phương pháp thứ hai có vẻ tốt hơn cả phương pháp hiện tại và phương pháp đầu tiên, với tôi. Nhưng tôi muốn biết các quan điểm của các nhà phát triển C++ có kinh nghiệm khác về nó. Hay tôi lo lắng quá nhiều về tầm thường. :-)

Trả lời

20

# 1 gây ô nhiễm không gian tên lớp và vtable cho các đối tượng không cần đến. Ok khi bạn có một số phương thức thường sẽ được thực hiện, nhưng đồng bằng xấu xí khi chỉ cần cho một lớp dẫn xuất duy nhất.

# 2 chỉ là dynamic_cast<> trong trang phục chấm bi và son môi. Không làm cho mã khách hàng đơn giản hơn, và rối lên toàn bộ hệ thống phân cấp, yêu cầu cơ sở và mỗi lớp dẫn xuất phải được nhận thức một cách mỗi lớp dẫn xuất khác.

Chỉ cần sử dụng dynamic_cast<>. Đó là những gì nó có cho.

4

Nếu bạn muốn thông minh, bạn cũng có thể tạo mẫu double dispatch, hai phần ba số visitor pattern.

  • Tạo cơ sở TokenVisitor lớp chứa phương thức ảo rỗng visit(SpecificToken*).
  • Thêm một phương thức ảo accept(TokenVisitor*) ảo vào Mã để gọi phương thức được nhập đúng cách trên TokenVisitor đã chuyển.
  • Bắt nguồn từ TokenVisitor cho nhiều thứ khác nhau mà bạn sẽ cần thực hiện theo nhiều cách khác nhau trên tất cả các mã thông báo.

Đối với mẫu khách truy cập đầy đủ, hữu ích cho cấu trúc cây, có các phương thức mặc định accept lặp lại trên trẻ em gọi token->accept(this); trên mỗi.

4

Nếu bạn biết chuyển đổi không thể không hợp lệ thì chỉ cần sử dụng static_cast.

+1

Đồng ý, nếu bạn đã kiểm tra loại có chức năng ảo, thì dynamic_cast là vô dụng. Cả hai giải pháp khác của bạn chỉ là màu trắng mà hệ thống phân cấp của bạn bị hỏng. Nếu bạn sẽ có một hack, ít nhất là sử dụng hack nhanh hơn. – BigSandwich

+1

static_cast sẽ không hoạt động nếu lớp dẫn xuất sử dụng thừa kế ảo để kế thừa từ Mã thông báo. dynamic_cast sẽ không vô ích trong trường hợp này. – bk1e

2

Tại sao bạn muốn tránh sử dụng dynamic_cast? Là nó gây ra một cổ chai không thể chấp nhận trên ứng dụng của bạn? Nếu không, nó có thể không có giá trị làm bất cứ điều gì để mã ngay bây giờ.

Nếu bạn đồng ý với việc giao dịch một số lượng an toàn cho một chút tốc độ trong trường hợp cụ thể của bạn, bạn nên làm một số static_cast; Tuy nhiên, điều này là củng cố giả định của bạn rằng bạn biết loại đối tượng và không có cơ hội nào của diễn viên xấu. Nếu giả định của bạn trở thành sai sau đó, bạn có thể kết thúc với một số lỗi tai nạn bí ẩn trong mã của bạn. Quay trở lại câu hỏi ban đầu của tôi, bạn có chắc chắn thương mại giảm giá trị ở đây không?

Đối với các tùy chọn bạn liệt kê: Đầu tiên không thực sự giống như một giải pháp nhiều như một phút cuối cùng tôi hy vọng sẽ thấy ném vào khi ai đó đang viết mã lúc 3 giờ sáng. Chức năng lừa theo cách của nó đối với các cơ sở của hệ thống phân cấp lớp là một trong những mô hình phổ biến nhất chống lại những người mới đến OOP. Đừng làm thế.

Đối với tùy chọn thứ hai bạn liệt kê bất kỳ tùy chọn nào thực sự chỉ cần thực hiện lại dynamic_cast - nếu bạn đang ở trên nền tảng chỉ có trình biên dịch crap (tôi đã nghe câu chuyện về trình biên dịch của Gamecube chiếm 1/4 dung lượng RAM của hệ thống) với thông tin RTTI) điều này có thể đáng giá, nhưng nhiều khả năng bạn chỉ lãng phí thời gian của mình. Bạn có thực sự chắc chắn đây là một cái gì đó đáng giá liên quan đến bản thân bạn?

+0

Không, tôi không chắc rằng điều này đáng quan tâm. Đó là lý do tại sao tôi hỏi. :-) –

+0

Như đã đề cập ở trên: static_cast không thành công nếu sử dụng thừa kế ảo. – mmmmmmmm

1

Cách sạch thực sự để ngăn dynamic_cast là có con trỏ đúng loại ở đúng vị trí. Trừu tượng nên chăm sóc phần còn lại.

IMHO, lý do dynamic_cast có danh tiếng này là do hiệu suất của nó giảm xuống một chút mỗi lần bạn thêm một loại phụ khác trong phân cấp lớp của mình. Nếu bạn có 4-5 lớp trong hệ thống phân cấp, không có gì phải lo lắng cả.

1

Nội dung thú vị. dynamic_cast trong trình mã thông báo ngụ ý rằng bạn muốn thực sự chia Mã thông báo thành thứ gì đó tạo Mã thông báo dựa trên vị trí hiện tại trong luồng văn bản và logic trong Mã thông báo. Mỗi mã thông báo sẽ được tự chứa hoặc phải tạo Mã thông báo giống như trên để xử lý văn bản chính xác.

Bằng cách đó, bạn lấy ra những thứ chung chung và vẫn có thể phân tích cú pháp dựa trên thứ bậc của mã thông báo. Bạn thậm chí có thể làm cho toàn bộ trình phân tích cú pháp này điều khiển dữ liệu mà không cần sử dụng dynamic_cast.