Hầu hết mọi tài nguyên C ++ mà tôi đã thấy thảo luận về loại điều này đều nói với tôi rằng tôi nên thích các cách tiếp cận đa hình hơn để sử dụng RTTI (nhận dạng kiểu thời gian chạy). Nói chung, tôi thực hiện loại lời khuyên này một cách nghiêm túc, và sẽ cố gắng tìm hiểu cơ sở lý luận - xét cho cùng, C ++ là một con quái vật hùng mạnh và khó hiểu được toàn bộ chiều sâu của nó. Tuy nhiên, đối với câu hỏi cụ thể này, tôi đang vẽ một ô trống và muốn xem Internet có thể đưa ra lời khuyên nào. Trước tiên, hãy để tôi tóm tắt những gì tôi đã học được cho đến nay, bằng cách liệt kê những lý do phổ biến được trích dẫn tại sao RTTI bị "coi là có hại":
Một số trình biên dịch không sử dụng nó / RTTI không phải lúc nào cũng được bật
Tôi thực sự không mua lập luận này. Nó giống như nói rằng tôi không nên sử dụng các tính năng của C ++ 14, bởi vì có những trình biên dịch ngoài kia không hỗ trợ nó. Tuy nhiên, không ai ngăn cản tôi sử dụng các tính năng của C ++ 14. Phần lớn các dự án sẽ có ảnh hưởng đến trình biên dịch mà họ đang sử dụng và cách nó được cấu hình. Ngay cả trích dẫn trang gcc:
-fno-rtti
Vô hiệu hóa việc tạo thông tin về mọi lớp với các hàm ảo để sử dụng bởi các tính năng nhận dạng thời gian chạy C ++ (dynamic_cast và typeid). Nếu bạn không sử dụng các phần đó của ngôn ngữ, bạn có thể tiết kiệm một số dung lượng bằng cách sử dụng cờ này. Lưu ý rằng xử lý ngoại lệ sử dụng cùng một thông tin, nhưng G ++ tạo ra nó khi cần thiết. Toán tử dynamic_cast vẫn có thể được sử dụng cho các phôi không yêu cầu thông tin kiểu thời gian chạy, tức là các phôi thành "void *" hoặc các lớp cơ sở rõ ràng.
Điều này cho tôi biết là nếu tôi không sử dụng RTTI, tôi có thể tắt nó. Điều đó giống như nói rằng, nếu bạn không sử dụng Boost, bạn không cần phải liên kết với nó. Tôi không phải lên kế hoạch cho trường hợp có ai đó đang biên dịch -fno-rtti
. Thêm vào đó, trình biên dịch sẽ không to và rõ ràng trong trường hợp này.
Nó tốn thêm bộ nhớ / Có thể chậm
Bất cứ khi nào tôi muốn sử dụng RTTI, điều đó có nghĩa là tôi cần truy cập vào một số loại thông tin hoặc đặc điểm của lớp mình. Nếu tôi triển khai một giải pháp không sử dụng RTTI, điều này thường có nghĩa là tôi sẽ phải thêm một số trường vào các lớp của mình để lưu trữ thông tin này, vì vậy đối số bộ nhớ là loại không có giá trị (tôi sẽ đưa ra ví dụ về điều này thêm).
Thực tế, một dynamic_cast có thể chậm. Tuy nhiên, thường có nhiều cách để tránh phải sử dụng nó trong các tình huống quan trọng. Và tôi không hoàn toàn thấy giải pháp thay thế. Câu trả lời SO này gợi ý sử dụng một enum, được định nghĩa trong lớp cơ sở, để lưu trữ kiểu. Điều đó chỉ hoạt động nếu bạn biết trước tất cả các lớp dẫn xuất của mình. Đó là một "nếu" khá lớn!
Từ câu trả lời đó, có vẻ như chi phí của RTTI cũng không rõ ràng. Những người khác nhau đo lường những thứ khác nhau.
Thiết kế đa hình thanh lịch sẽ làm cho RTTI không cần thiết
Đây là loại lời khuyên mà tôi thực hiện nghiêm túc. Trong trường hợp này, tôi chỉ đơn giản là không thể đưa ra các giải pháp không phải RTTI phù hợp với trường hợp sử dụng RTTI của tôi. Hãy để tôi cung cấp một ví dụ:
Giả sử tôi đang viết một thư viện để xử lý đồ thị của một số loại đối tượng. Tôi muốn cho phép người dùng tạo các kiểu của riêng họ khi sử dụng thư viện của tôi (vì vậy phương thức enum không khả dụng). Tôi có một lớp cơ sở cho nút của mình:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
Bây giờ, các nút của tôi có thể thuộc nhiều loại khác nhau. Làm thế nào về những điều này:
class red_node : virtual public node_base
{
public:
red_node();
virtual ~red_node();
void get_redness();
};
class yellow_node : virtual public node_base
{
public:
yellow_node();
virtual ~yellow_node();
void set_yellowness(int);
};
Quỷ thần ơi, tại sao lại không có một trong những cái này
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
Chức năng cuối cùng là thú vị. Đây là một cách để viết nó:
void orange_node::poke_adjacent_oranges()
{
auto adj_nodes = get_adjacent_nodes();
foreach(auto node, adj_nodes) {
// In this case, typeid() and static_cast might be faster
std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
if (o_node) {
o_node->poke();
}
}
}
Tất cả điều này có vẻ rõ ràng và sạch sẽ. Tôi không phải xác định các thuộc tính hoặc phương thức mà tôi không cần đến chúng, lớp nút cơ sở có thể duy trì tốt và có ý nghĩa. Nếu không có RTTI, tôi phải bắt đầu từ đâu? Có lẽ tôi có thể thêm thuộc tính node_type vào lớp cơ sở:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
private:
std::string my_type;
};
Std :: string có phải là một ý tưởng tốt cho một kiểu không? Có thể không, nhưng tôi có thể dùng gì khác? Tạo một số và hy vọng không có ai khác đang sử dụng nó? Ngoài ra, trong trường hợp của tôi với Orange_node, nếu tôi muốn sử dụng các phương thức từ red_node và yellow_node thì sao? Tôi có phải lưu trữ nhiều loại trên mỗi nút không? Điều đó có vẻ phức tạp.
Phần kết luận
Ví dụ này có vẻ không quá phức tạp hoặc bất thường (tôi đang làm việc tương tự trong công việc hàng ngày của tôi, nơi các nút đại diện cho phần cứng thực tế được điều khiển thông qua phần mềm và hoạt động rất khác nhau tùy thuộc vào chúng là gì). Tuy nhiên, tôi sẽ không biết một cách rõ ràng để làm điều này với các mẫu hoặc các phương pháp khác. Xin lưu ý rằng tôi đang cố gắng hiểu vấn đề, không bảo vệ ví dụ của mình. Việc tôi đọc các trang như câu trả lời SO mà tôi đã liên kết ở trên và trang này trên Wikibooks dường như cho thấy tôi đang lạm dụng RTTI, nhưng tôi muốn tìm hiểu tại sao.
Vì vậy, trở lại câu hỏi ban đầu của tôi: Tại sao 'tính đa hình thuần túy' lại được ưa chuộng hơn sử dụng RTTI?
node_base
là một phần của thư viện và người dùng sẽ tạo các loại nút của riêng họ. Sau đó, họ không thể sửa đổi node_base
để cho phép một giải pháp khác, vì vậy có thể RTTI trở thành lựa chọn tốt nhất khi đó. Mặt khác, có nhiều cách khác để thiết kế một thư viện như vậy sao cho các loại nút mới có thể phù hợp hơn một cách trang nhã hơn mà không cần sử dụng RTTI (và các cách khác để thiết kế các loại nút mới).