Tại sao 'đa hình thuần túy' lại được ưa chuộng hơn sử dụng RTTI?


106

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?


9
Những gì bạn "thiếu" (như một tính năng ngôn ngữ) để giải quyết ví dụ poke cam của bạn sẽ là nhiều công văn ("multimethods"). Vì vậy, tìm kiếm cách để mô phỏng đó có thể là một giải pháp thay thế. Thông thường, mẫu khách truy cập được sử dụng do đó.
Daniel Jour

1
Sử dụng một chuỗi làm kiểu không hữu ích lắm. Sử dụng một con trỏ đến một thể hiện của một số lớp "type" sẽ làm cho việc này nhanh hơn. Nhưng về cơ bản thì bạn đang làm thủ công những gì RTTI sẽ làm.
Daniel Jour

4
@MargaretBloom Không, nó sẽ không, RTTI là viết tắt của Runtime Type Information trong khi CRTP chỉ dành cho các mẫu - loại tĩnh, vì vậy.
edmz

2
@ mbr0wn: tất cả các quy trình kỹ thuật đều bị ràng buộc bởi một số quy tắc; lập trình không phải là một ngoại lệ. Các quy tắc có thể được chia thành hai nhóm: quy tắc mềm (NÊN) và quy tắc cứng (PHẢI). (Cũng có một lời khuyên / nhóm tùy chọn (CO N), có thể nói như vậy.) Đọc cách tiêu chuẩn C / C ++ (hoặc bất kỳ tiêu chuẩn tương tác nào khác, trên thực tế) định nghĩa chúng. Tôi đoán vấn đề của bạn xuất phát từ việc bạn đã nhầm lẫn "không sử dụng RTTI" như một quy tắc cứng ("bạn KHÔNG sử dụng RTTI"). Nó thực sự là một quy tắc mềm ( "Bạn không nên sử dụng RTTI"), có nghĩa là bạn nên tránh nó bất cứ khi nào có thể - và chỉ sử dụng khi bạn không thể tránh làm như vậy

3
Tôi lưu ý rằng nhiều câu trả lời không lưu ý rằng ví dụ của bạn gợi ý node_baselà 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).
Matthew Walton

Câu trả lời:


69

Một giao diện mô tả những gì người ta cần biết để tương tác trong một tình huống nhất định trong mã. Khi bạn mở rộng giao diện với "toàn bộ loại phân cấp", "diện tích bề mặt" giao diện của bạn sẽ trở nên rất lớn, điều này làm cho việc lập luận về nó trở nên khó khăn hơn .

Ví dụ: "chọc những quả cam liền kề" của bạn có nghĩa là tôi, với tư cách là bên thứ 3, không thể mô phỏng trở thành một quả cam! Bạn đã khai báo riêng một kiểu màu cam, sau đó sử dụng RTTI để làm cho mã của bạn hoạt động đặc biệt khi tương tác với kiểu đó. Nếu tôi muốn "trở thành quả cam", tôi phải ở trong khu vườn riêng của bạn.

Giờ đây, tất cả những ai có "độ cam" đều kết hợp với toàn bộ loại màu cam của bạn và mặc nhiên với toàn bộ khu vườn riêng của bạn, thay vì với một giao diện xác định.

Mặc dù thoạt nhìn, đây có vẻ là một cách tuyệt vời để mở rộng giao diện giới hạn mà không cần phải thay đổi tất cả các máy khách (thêm am_I_orange), nhưng điều có xu hướng xảy ra thay vào đó là nó chỉ định cơ sở mã và ngăn không cho mở rộng thêm. Màu cam đặc biệt trở nên cố hữu đối với hoạt động của hệ thống và ngăn bạn tạo ra sự thay thế "quýt" cho màu cam được triển khai khác và có thể loại bỏ sự phụ thuộc hoặc giải quyết một số vấn đề khác một cách thanh lịch.

Điều này có nghĩa là giao diện của bạn phải đủ để giải quyết vấn đề của bạn. Từ góc độ đó, tại sao bạn chỉ cần chọc vào quả cam, và nếu vậy thì tại sao màu cam lại không có trong giao diện? Nếu bạn cần một số thẻ mờ có thể được thêm vào đặc biệt, bạn có thể thêm thẻ đó vào loại của mình:

class node_base {
  public:
    bool has_tag(tag_name);

Điều này cung cấp một sự mở rộng lớn tương tự cho giao diện của bạn từ chỉ định hẹp sang dựa trên thẻ rộng. Ngoại trừ thay vì làm điều đó thông qua RTTI và chi tiết triển khai (hay còn gọi là "bạn được triển khai như thế nào? Với loại màu cam? Ok bạn vượt qua."), Nó làm như vậy với một cái gì đó dễ dàng được mô phỏng thông qua một triển khai hoàn toàn khác.

Điều này thậm chí có thể được mở rộng cho các phương thức động, nếu bạn cần. "Bạn có ủng hộ việc trở thành Foo'd với những lập luận Baz, Tom và Alice không? Ok, lừa bạn." Theo một nghĩa lớn, điều này ít xâm phạm hơn so với một diễn viên động để biết được thực tế đối tượng khác là một loại mà bạn biết.

Giờ đây, các đối tượng quýt có thể có thẻ màu cam và chơi cùng, trong khi được phân tách triển khai.

Nó vẫn có thể dẫn đến một mớ hỗn độn lớn, nhưng ít nhất nó là một mớ hỗn độn của các thông điệp và dữ liệu, không phải là phân cấp triển khai.

Trừu tượng là một trò chơi tách rời và che giấu những điều không thể xử lý. Nó làm cho mã dễ dàng hơn để suy luận về cục bộ. RTTI đang khoét một lỗ thẳng thông qua sự trừu tượng hóa thành các chi tiết triển khai. Điều này có thể làm cho việc giải quyết một vấn đề dễ dàng hơn, nhưng nó có cái giá phải trả là khóa bạn vào một triển khai cụ thể thực sự dễ dàng.


14
+1 cho đoạn cuối cùng; không chỉ bởi vì tôi đồng ý với bạn, mà bởi vì nó là cái búa ở đây.

7
Làm thế nào để một người có được chức năng cụ thể sau khi biết rằng một đối tượng được gắn thẻ là hỗ trợ chức năng đó? Điều này liên quan đến việc ép kiểu, hoặc có lớp God với mọi chức năng thành viên có thể. Khả năng đầu tiên là truyền không được kiểm tra, trong trường hợp đó, việc gắn thẻ chỉ là lược đồ kiểm tra kiểu động rất dễ sai sót của riêng một người hoặc nó được kiểm tra dynamic_cast(RTTI), trong trường hợp này các thẻ là thừa. Khả năng thứ hai, một hạng Thượng đế, là đáng ghê tởm. Tóm lại, câu trả lời này có nhiều từ mà tôi nghĩ nghe có vẻ hay đối với các lập trình viên Java, nhưng nội dung thực tế là vô nghĩa.
Chúc mừng và hth. - Alf

2
@Falco: Đó là (một biến thể của) khả năng đầu tiên mà tôi đã đề cập, truyền không được chọn dựa trên thẻ. Ở đây, việc gắn thẻ là một lược đồ kiểm tra kiểu động rất giòn và rất dễ vỡ. Bất kỳ hành vi sai mã khách hàng nhỏ nào và trong C ++, một hành vi sai sót trong UB-land đã bị tắt. Bạn không nhận được ngoại lệ, như bạn có thể gặp trong Java, nhưng Hành vi không xác định, chẳng hạn như sự cố và / hoặc kết quả không chính xác. Ngoài việc cực kỳ không đáng tin cậy và nguy hiểm, nó cũng cực kỳ kém hiệu quả, so với mã C ++ lành mạnh hơn. IOW., Nó rất không tốt; cực kỳ như vậy.
Chúc mừng và hth. - Alf

1
Uhm. :) Các kiểu lập luận?
Chúc mừng và hth. - Alf

2
@JojOatXGME: Bởi vì "đa hình" có nghĩa là có thể làm việc với nhiều loại. Nếu bạn phải kiểm tra xem nó có phải là một kiểu cụ thể hay không , ngoài việc kiểm tra kiểu đã tồn tại mà bạn đã sử dụng để bắt đầu con trỏ / tham chiếu, thì bạn đang tìm kiếm sự đa hình. Bạn không làm việc với nhiều loại; bạn đang làm việc với một loại cụ thể . Có, có những dự án "(lớn) trong Java" làm điều này. Nhưng đó là Java ; ngôn ngữ chỉ cho phép đa hình động. C ++ cũng có tính đa hình tĩnh. Ngoài ra, chỉ vì một ai đó "lớn" làm điều đó không phải là một ý tưởng tốt.
Nicol Bolas

31

Hầu hết những lời thuyết phục về mặt đạo đức chống lại tính năng này hoặc tính năng đó là tính điển hình bắt nguồn từ việc quan sát thấy rằng có một số cách sử dụng sai lầm của tính năng đó.

Trường hợp các nhà đạo đức thất bại là họ cho rằng TẤT CẢ các cách sử dụng đều bị hiểu sai, trong khi thực tế các đặc điểm tồn tại là có lý do.

Họ có cái mà tôi từng gọi là "tổ hợp thợ sửa ống nước": họ nghĩ rằng tất cả các vòi đều bị trục trặc vì tất cả các vòi mà họ được gọi là để sửa chữa. Thực tế là hầu hết các vòi đều hoạt động tốt: đơn giản là bạn không cần gọi thợ sửa ống nước cho chúng!

Một điều điên rồ có thể xảy ra là khi, để tránh sử dụng một tính năng nhất định, các lập trình viên viết rất nhiều mã soạn sẵn thực sự riêng tư để triển khai lại chính xác tính năng đó. (Bạn đã bao giờ gặp các lớp không sử dụng RTTI cũng như các cuộc gọi ảo, nhưng có giá trị để theo dõi chúng thuộc loại dẫn xuất thực tế nào chưa? Đó không chỉ là sự tái tạo RTTI dưới dạng ngụy trang.)

Có một cách tổng quát để suy nghĩ về đa hình: IF(selection) CALL(something) WITH(parameters). (Xin lỗi, nhưng lập trình, khi bỏ qua sự trừu tượng, là tất cả về điều đó)

Việc sử dụng thời gian thiết kế (khái niệm) thời gian biên dịch (dựa trên khuôn mẫu), thời gian chạy (kế thừa và dựa trên chức năng ảo) hoặc đa hình theo hướng dữ liệu (RTTI và chuyển mạch), phụ thuộc vào mức độ quyết định được biết đến tại mỗi giai đoạn của quá trình sản xuất và mức độ thay đổi của chúng ở mọi bối cảnh.

Ý tưởng là:

bạn càng dự đoán được nhiều thì cơ hội bắt lỗi và tránh lỗi ảnh hưởng đến người dùng cuối càng cao.

Nếu mọi thứ không đổi (bao gồm cả dữ liệu), bạn có thể làm mọi thứ với lập trình siêu mẫu. Sau khi quá trình biên dịch diễn ra trên các hằng số được thực tế hóa, toàn bộ chương trình sẽ trở thành chỉ một câu lệnh trả về đưa ra kết quả .

Nếu có một số trường hợp đã biết tất cả tại thời điểm biên dịch , nhưng bạn không biết về dữ liệu thực tế mà chúng phải xử lý, thì đa hình thời gian biên dịch (chủ yếu là CRTP hoặc tương tự) có thể là một giải pháp.

Nếu việc lựa chọn các trường hợp phụ thuộc vào dữ liệu (không phải giá trị đã biết theo thời gian biên dịch) và chuyển mạch là đơn chiều (những việc cần làm chỉ có thể giảm xuống một giá trị) thì điều khiển dựa trên hàm ảo (hoặc nói chung là "bảng con trỏ hàm ") là cần thiết.

Nếu quá trình chuyển đổi là nhiều chiều, vì không có điều phối thời gian chạy nhiều bản địa nào tồn tại trong C ++, thì bạn phải:

  • Giảm xuống một chiều bằng Goedelization : đó là nơi chứa các cơ sở ảo và đa kế thừa, với các viên kim cương và các hình bình hành xếp chồng lên nhau , nhưng điều này yêu cầu số lượng kết hợp có thể được biết đến và tương đối nhỏ.
  • Chuỗi các kích thước này thành kích thước khác (giống như trong mô hình khách truy cập tổng hợp, nhưng điều này yêu cầu tất cả các lớp phải nhận thức được các anh chị em khác của họ, do đó nó không thể "mở rộng" ra khỏi vị trí đã hình thành)
  • Cử cuộc gọi dựa trên nhiều giá trị. Đó chính xác là những gì RTTI dành cho.

Nếu không chỉ chuyển đổi, mà ngay cả các hành động không được xác định thời gian biên dịch, thì kịch bản và phân tích cú pháp là bắt buộc: bản thân dữ liệu phải mô tả hành động được thực hiện trên chúng.

Bây giờ, vì mỗi trường hợp tôi liệt kê có thể được coi là một trường hợp cụ thể của những gì tiếp theo, bạn có thể giải quyết mọi vấn đề bằng cách lạm dụng giải pháp thấp nhất cũng cho các vấn đề có giá cả phải chăng với giải cao nhất.

Đó là điều mà đạo đức hóa thực sự cần phải tránh. Nhưng điều đó không có nghĩa là không tồn tại các vấn đề sống ở những vùng thấp nhất!

Bashing RTTI chỉ để bash nó, giống như gotobash nó chỉ để bash nó. Những thứ dành cho vẹt, không phải lập trình viên.


Một tài khoản tốt về các cấp độ mà mỗi cách tiếp cận có thể áp dụng. Tôi chưa nghe nói về "Goedelization" - nó còn được biết đến với một số tên khác? Bạn có thể thêm một liên kết hoặc giải thích nhiều hơn? Cảm ơn :)
j_random_hacker

1
@j_random_hacker: Tôi cũng tò mò về việc sử dụng Godelization này. Người ta thường nghĩ về Godelization là thứ nhất, ánh xạ từ một số chuỗi sang một số nguyên và thứ hai, sử dụng kỹ thuật này để tạo ra các câu lệnh tự tham chiếu trong các ngôn ngữ chính thức. Tôi không quen với thuật ngữ này trong bối cảnh công văn ảo và rất muốn tìm hiểu thêm.
Eric Lippert

1
Trên thực tế, tôi đang lạm dụng thuật ngữ: theo Goedle, vì mọi số nguyên tương ứng với một số nguyên n-ple (lũy thừa của các thừa số nguyên tố của nó) và mọi n-ple tương ứng với một số nguyên, mọi vấn đề lập chỉ mục n-chiều rời rạc đều có thể giảm thành đơn chiều . Điều đó không có nghĩa rằng đây là cách duy nhất để làm điều đó: nó chỉ là một cách để nói "nó có thể". Tất cả những gì bạn cần là một cơ chế "chia để trị". các hàm ảo là "chia" và đa kế thừa là "chinh phục".
Emilio Garavaglia

... Khi tất cả những điều đó xảy ra bên trong một trường hữu hạn (một phạm vi) kết hợp tuyến tính hiệu quả hơn (i = r * C + c cổ điển nhận chỉ số trong một mảng ô của ma trận). Trong trường hợp này, id phân chia cho "khách truy cập" và chinh phục là "tổng hợp". Kể từ khi đại số tuyến tính là tham gia, kỹ thuật trong trường hợp này tương ứng với "diagonalization"
Emilio Garavaglia

Đừng nghĩ tất cả những điều này là kỹ thuật. Họ chỉ là loại suy
Emilio Garavaglia

23

Trong một ví dụ nhỏ trông có vẻ khá gọn gàng, nhưng trong cuộc sống thực, bạn sẽ sớm kết thúc với một tập hợp dài các loại có thể chọc vào nhau, một số trong số chúng có lẽ chỉ theo một hướng.

Điều gì về dark_orange_node, hoặc black_and_orange_striped_node, hoặc dotted_node? Nó có thể có các chấm màu sắc khác nhau? Điều gì sẽ xảy ra nếu hầu hết các chấm đều có màu cam, vậy nó có thể bị chọc vào không?

Và mỗi lần bạn phải thêm quy tắc mới, bạn sẽ phải truy cập lại tất cả các poke_adjacentchức năng và thêm nhiều câu lệnh if hơn.


Như mọi khi, thật khó để tạo ra các ví dụ chung chung, tôi sẽ cung cấp cho bạn điều đó.

Nhưng nếu tôi làm ví dụ cụ thể này , tôi sẽ thêm một poke()thành viên vào tất cả các lớp và để một số người trong số họ bỏ qua lệnh gọi ( void poke() {}) nếu họ không quan tâm.

Chắc chắn rằng điều đó sẽ thậm chí còn ít tốn kém hơn so với so sánh các typeids.


3
Bạn nói "chắc chắn", nhưng điều gì khiến bạn chắc chắn như vậy? Đó thực sự là những gì tôi đang cố gắng tìm ra. Giả sử tôi đổi tên cam_node thành pokable_node và chúng là những thứ duy nhất tôi có thể gọi là poke (). Điều đó có nghĩa là giao diện của tôi sẽ cần triển khai một phương thức poke (), ví dụ, ném một ngoại lệ ("nút này không thể tạo được"). Điều đó có vẻ nhiều tốn kém.
mbr0wn

2
Tại sao anh ta cần phải ném một ngoại lệ? Nếu bạn quan tâm xem giao diện có "poke-can" hay không, chỉ cần thêm một chức năng "isPokable" và gọi nó trước khi gọi chức năng poke. Hoặc chỉ làm những gì anh ta nói và "không làm gì, trong các lớp học không dễ bị chọc tức".
Brandon

1
@ mbr0wn: Câu hỏi hay hơn là tại sao bạn muốn các nút có thể di chuyển và không thể di chuyển được chia sẻ cùng một lớp cơ sở.
Nicol Bolas

2
@NicolBolas Tại sao bạn lại muốn những con quái vật thân thiện và thù địch chia sẻ cùng một lớp cơ sở hoặc các phần tử giao diện người dùng có thể lấy tiêu điểm và không thể lấy tiêu điểm, hoặc bàn phím có đệm và bàn phím không có đệm?
user253751 Ngày

1
@ mbr0wn Điều này nghe giống như mô hình hành vi. Giao diện cơ sở có hai phương pháp supportsBehaviourinvokeBehaviourvà mỗi lớp có thể có một danh sách các hành vi. Một hành vi sẽ là Poke và có thể được thêm vào danh sách các Hành vi được hỗ trợ bởi tất cả các lớp học muốn trở thành người có thể chọc phá.
Falco

20

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 tin rằng bạn đã hiểu sai những lập luận như vậy.

Có một số nơi mã hóa C ++ không sử dụng RTTI. Trường hợp các công tắc trình biên dịch được sử dụng để vô hiệu hóa cưỡng bức RTTI. Nếu bạn đang viết mã trong một mô hình như vậy ... thì bạn gần như chắc chắn đã được thông báo về hạn chế này.

Do đó, vấn đề là với các thư viện . Nghĩa là, nếu bạn đang viết thư viện phụ thuộc vào RTTI, thì thư viện của bạn không thể được sử dụng bởi những người dùng tắt RTTI. Nếu bạn muốn những người đó sử dụng thư viện của mình thì thư viện đó không thể sử dụng RTTI, ngay cả khi thư viện của bạn cũng được những người có thể sử dụng RTTI sử dụng. Quan trọng không kém, nếu bạn không thể sử dụng RTTI, bạn phải mua sắm thư viện khó hơn một chút, vì việc sử dụng RTTI là một yếu tố phá vỡ thỏa thuận đối với bạn.

Nó tốn thêm bộ nhớ / Có thể chậm

Có nhiều điều bạn không làm trong vòng lặp nóng. Bạn không phân bổ bộ nhớ. Bạn không lặp lại các danh sách được liên kết. Và kể từ đó trở đi. RTTI chắc chắn có thể là một trong những thứ "đừng làm điều này ở đây".

Tuy nhiên, hãy xem xét tất cả các ví dụ RTTI của bạn. Trong mọi trường hợp, bạn có một hoặc nhiều đối tượng thuộc loại không xác định và bạn muốn thực hiện một số thao tác trên chúng mà có thể không thực hiện được đối với một số đối tượng.

Đó là thứ bạn phải làm việc ở cấp độ thiết kế . Bạn có thể ghi các vùng chứa không phân bổ bộ nhớ phù hợp với mô hình "STL". Bạn có thể tránh các cấu trúc dữ liệu danh sách được liên kết hoặc hạn chế việc sử dụng chúng. Bạn có thể tổ chức lại các mảng cấu trúc thành cấu trúc mảng hoặc bất cứ thứ gì. Nó thay đổi một số thứ, nhưng bạn có thể giữ nó được ngăn nắp.

Thay đổi một hoạt động RTTI phức tạp thành một lệnh gọi hàm ảo thông thường? Đó là một vấn đề thiết kế. Nếu bạn phải thay đổi điều đó, thì đó là thứ yêu cầu thay đổi đối với mọi lớp dẫn xuất. Nó thay đổi cách rất nhiều mã tương tác với các lớp khác nhau. Phạm vi của sự thay đổi như vậy vượt xa các phần mã quan trọng về hiệu suất.

Vậy ... tại sao bạn lại viết sai cách bắt đầu?

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.

Để làm gì?

Bạn nói rằng lớp cơ sở là "nạc và có nghĩa". Nhưng thực sự ... nó không tồn tại . Nó không thực sự làm bất cứ điều gì .

Chỉ cần nhìn vào ví dụ của bạn: node_base. Nó là gì? Nó dường như là một thứ có những thứ khác liền kề. Đây là một giao diện Java (tại đó là Java pre-generics): một lớp tồn tại duy nhất để trở thành thứ mà người dùng có thể truyền sang kiểu thực . Có thể bạn thêm một số tính năng cơ bản như kề (Java thêmToString ), nhưng chỉ có vậy.

Có sự khác biệt giữa "tinh gọn và trung bình" và "minh bạch".

Như Yakk đã nói, các kiểu lập trình như vậy tự giới hạn khả năng tương tác, bởi vì nếu tất cả các chức năng nằm trong một lớp dẫn xuất, thì người dùng bên ngoài hệ thống đó, không có quyền truy cập vào lớp dẫn xuất đó, không thể tương tác với hệ thống. Chúng không thể ghi đè các chức năng ảo và thêm các hành vi mới. Họ thậm chí không thể gọi các chức năng đó.

Nhưng những gì họ cũng làm là khiến việc thực sự làm những thứ mới, ngay cả trong hệ thống trở nên khó khăn. Xem xét poke_adjacent_orangeschức năng của bạn . Điều gì xảy ra nếu ai đó muốn một lime_nodeloại có thể được chọc giống như orange_nodes? Chà, chúng ta không thể bắt nguồn lime_nodetừ orange_node; điều đó không có ý nghĩa.

Thay vào đó, chúng ta phải thêm một từ mới lime_nodecó nguồn gốc node_base. Sau đó, thay đổi tên của poke_adjacent_orangesthành poke_adjacent_pokables. Và sau đó, hãy thử truyền đến orange_nodelime_node; chúng tôi chọc vào bất cứ diễn viên nào làm việc.

Tuy nhiên, lime_nodenhu cầu của riêng nó poke_adjacent_pokables . Và chức năng này cần thực hiện các kiểm tra đúc tương tự.

Và nếu chúng ta thêm một kiểu thứ ba, chúng ta không chỉ phải thêm chức năng của riêng nó mà còn phải thay đổi các chức năng trong hai lớp còn lại.

Rõ ràng, bây giờ bạn tạo poke_adjacent_pokablesmột chức năng miễn phí, để nó hoạt động cho tất cả chúng. Nhưng bạn giả sử điều gì sẽ xảy ra nếu ai đó thêm loại thứ tư và quên thêm nó vào chức năng đó?

Xin chào, sự đổ vỡ im lặng . Chương trình dường như hoạt động ít nhiều thì OK, nhưng không phải vậy. Đã pokelà một hàm ảo thực sự , trình biên dịch sẽ không thành công khi bạn không ghi đè lên hàm ảo thuần túy từ đó node_base.

Với cách của bạn, bạn không có kiểm tra trình biên dịch nào như vậy. Ồ chắc chắn, trình biên dịch sẽ không kiểm tra các hình ảnh ảo không thuần túy, nhưng ít nhất bạn có bảo vệ trong các trường hợp có thể bảo vệ (tức là: không có hoạt động mặc định).

Việc sử dụng các lớp cơ sở trong suốt với RTTI dẫn đến một cơn ác mộng về bảo trì. Thật vậy, hầu hết việc sử dụng RTTI đều dẫn đến đau đầu về bảo trì. Điều đó không có nghĩa là RTTI không hữu ích ( boost::anyví dụ, nó rất quan trọng đối với công việc). Nhưng nó là một công cụ rất chuyên biệt cho những nhu cầu rất chuyên biệt.

Theo cách đó, nó là "có hại" theo cách tương tự như goto. Đó là một công cụ hữu ích mà bạn không nên bỏ qua. Nhưng nó hiếm khi được sử dụng trong mã của bạn.


Vì vậy, nếu bạn không thể sử dụng các lớp cơ sở trong suốt và đúc động, làm thế nào để bạn tránh các giao diện béo? Làm cách nào để bạn không bị sủi bọt ở mọi hàm mà bạn có thể muốn gọi trên một loại từ sôi lên đến lớp cơ sở?

Câu trả lời phụ thuộc vào lớp cơ sở dùng để làm gì.

Các lớp cơ sở trong suốt giống như node_baseđang sử dụng sai công cụ cho vấn đề. Danh sách liên kết được xử lý tốt nhất bởi các mẫu. Loại nút và vùng kề sẽ được cung cấp bởi một loại mẫu. Nếu bạn muốn đặt một kiểu đa hình trong danh sách, bạn có thể. Chỉ cần sử dụng BaseClass*như Ttrong đối số mẫu. Hoặc con trỏ thông minh ưa thích của bạn.

Nhưng có những kịch bản khác. Một là loại làm được nhiều thứ, nhưng có một số phần tùy chọn. Một phiên bản cụ thể có thể triển khai các chức năng nhất định, trong khi một phiên bản khác thì không. Tuy nhiên, thiết kế của các loại như vậy thường cung cấp một câu trả lời thích hợp.

Lớp "thực thể" là một ví dụ hoàn hảo về điều này. Lớp học này từ lâu đã gây khó khăn cho các nhà phát triển trò chơi. Về mặt khái niệm, nó có một giao diện khổng lồ, sống ở giao điểm của gần một tá hệ thống hoàn toàn khác nhau. Và các thực thể khác nhau có các thuộc tính khác nhau. Một số thực thể không có bất kỳ biểu diễn trực quan nào, vì vậy các chức năng kết xuất của chúng không có tác dụng gì. Và tất cả điều này được xác định trong thời gian chạy.

Giải pháp hiện đại cho điều này là một hệ thống kiểu thành phần. Entitychỉ đơn thuần là một thùng chứa một tập hợp các thành phần, với một số chất keo giữa chúng. Một số thành phần là tùy chọn; một thực thể không có hình ảnh đại diện không có thành phần "đồ họa". Một thực thể không có AI sẽ không có thành phần "bộ điều khiển". Và kể từ đó trở đi.

Các thực thể trong một hệ thống như vậy chỉ là con trỏ đến các thành phần, với hầu hết giao diện của chúng được cung cấp bằng cách truy cập trực tiếp vào các thành phần.

Việc phát triển một hệ thống thành phần như vậy đòi hỏi phải nhận ra, ở giai đoạn thiết kế, một số chức năng nhất định được nhóm lại với nhau về mặt khái niệm, sao cho tất cả các loại thực hiện một sẽ thực hiện tất cả chúng. Điều này cho phép bạn trích xuất lớp từ lớp cơ sở tiềm năng và biến nó thành một thành phần riêng biệt.

Điều này cũng giúp tuân theo Nguyên tắc Trách nhiệm Đơn lẻ. Một lớp được thành phần hóa như vậy chỉ có trách nhiệm là người nắm giữ các thành phần.


Từ Matthew Walton:

Tôi lưu ý rằng nhiều câu trả lời không lưu ý ý tưởng rằng ví dụ của bạn cho thấy 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 đó.

OK, hãy khám phá điều đó.

Để điều này có ý nghĩa, những gì bạn sẽ phải có là một tình huống trong đó một số thư viện L cung cấp một vùng chứa hoặc người giữ dữ liệu có cấu trúc khác. Người dùng có thể thêm dữ liệu vào vùng chứa này, lặp lại nội dung của nó, v.v. Tuy nhiên, thư viện không thực sự làm gì với dữ liệu này; nó chỉ đơn giản là quản lý sự tồn tại của nó.

Nhưng nó thậm chí không quản lý sự tồn tại của nó nhiều như sự tàn phá của nó . Lý do là, nếu bạn muốn sử dụng RTTI cho những mục đích như vậy, thì bạn đang tạo ra các lớp mà L không biết. Điều này có nghĩa là mã của bạn phân bổ đối tượng và giao nó cho L quản lý.

Bây giờ, có những trường hợp một cái gì đó như thế này là một thiết kế hợp pháp. Báo hiệu sự kiện / truyền thông điệp, hàng đợi công việc an toàn theo luồng, v.v. Mô hình chung ở đây là: ai đó đang thực hiện một dịch vụ giữa hai đoạn mã phù hợp với bất kỳ loại nào, nhưng dịch vụ không cần biết về các loại cụ thể liên quan .

Trong C, mẫu này được đánh vần void*, và việc sử dụng nó đòi hỏi rất nhiều cẩn thận để tránh bị hỏng. Trong C ++, mẫu này được đánh vần std::experimental::any(sắp sửa được viết std::any).

Cách này phải hoạt động là L cung cấp một node_baselớp lấy một anyđại diện cho dữ liệu thực tế của bạn. Khi bạn nhận được tin nhắn, mục công việc của hàng đợi luồng hoặc bất cứ điều gì bạn đang làm, sau đó bạn truyền nó anysang loại thích hợp mà cả người gửi và người nhận đều biết.

Vì vậy, thay vì bắt nguồn orange_nodetừ node_data, bạn chỉ cần dính vào một orangebên trong node_datacủa anytrường thành viên. Người dùng cuối trích xuất nó và sử dụng any_castđể chuyển đổi nó thành orange. Nếu quá trình cast không thành công, thì nó đã không phải orange.

Bây giờ, nếu bạn đã quen với việc triển khai any, bạn có thể sẽ nói, "này, chờ một chút: any nội bộ sử dụng RTTI để any_casthoạt động." Tôi trả lời là "... có".

Đó là điểm của một sự trừu tượng . Đi sâu vào chi tiết, ai đó đang sử dụng RTTI. Nhưng ở cấp độ bạn phải hoạt động, RTTI trực tiếp không phải là điều bạn nên làm.

Bạn nên sử dụng các loại cung cấp cho bạn chức năng bạn muốn. Sau tất cả, bạn không thực sự muốn RTTI. Những gì bạn muốn là một cấu trúc dữ liệu có thể lưu trữ giá trị của một kiểu nhất định, ẩn nó khỏi mọi người ngoại trừ đích mong muốn, sau đó được chuyển đổi trở lại thành kiểu đó, với xác minh rằng giá trị được lưu trữ thực sự thuộc kiểu đó.

Đó được gọi là any. Nó sử dụng RTTI, nhưng việc sử dụng anyvượt trội hơn nhiều so với sử dụng RTTI trực tiếp, vì nó phù hợp với ngữ nghĩa mong muốn một cách chính xác hơn.


10

Nếu bạn gọi một hàm, theo nguyên tắc, bạn không thực sự quan tâm nó sẽ thực hiện những bước chính xác nào, chỉ rằng một số mục tiêu cấp cao hơn sẽ đạt được trong những ràng buộc nhất định (và cách hàm thực hiện điều đó thực sự là vấn đề của riêng bạn).

Khi bạn sử dụng RTTI để lựa chọn trước các đối tượng đặc biệt có thể thực hiện một công việc nhất định, trong khi những đối tượng khác trong cùng một tập hợp không thể, bạn đang phá vỡ quan điểm thoải mái đó về thế giới. Đột nhiên, người gọi được cho là phải biết ai có thể làm gì, thay vì chỉ đơn giản nói với tay sai của mình để làm điều đó. Một số người cảm thấy phiền vì điều này, và tôi nghi ngờ đây là một phần lớn lý do tại sao RTTI bị coi là hơi bẩn.

Có vấn đề về hiệu suất không? Có thể, nhưng tôi chưa bao giờ trải nghiệm nó, và đó có thể là sự khôn ngoan từ hai mươi năm trước, hoặc từ những người thành thật tin rằng việc sử dụng ba hướng dẫn lắp ráp thay vì hai là điều không thể chấp nhận được.

Vì vậy, làm thế nào để đối phó với nó ... Tùy thuộc vào tình huống của bạn, có thể có ý nghĩa khi có bất kỳ thuộc tính cụ thể của nút nào được đóng gói thành các đối tượng riêng biệt (nghĩa là toàn bộ API 'màu cam' có thể là một đối tượng riêng biệt). Đối tượng gốc sau đó có thể có một hàm ảo để trả về API 'màu cam', trả về nullptr theo mặc định cho các đối tượng không phải màu cam.

Mặc dù điều này có thể quá mức cần thiết tùy thuộc vào tình huống của bạn, nhưng nó sẽ cho phép bạn truy vấn ở cấp gốc xem một nút cụ thể có hỗ trợ một API cụ thể hay không và nếu có, hãy thực thi các chức năng dành riêng cho API đó.


6
Re: chi phí hiệu suất - Tôi đo dynamic_cast <> là tốn khoảng 2µs trong ứng dụng của chúng tôi trên bộ xử lý 3GHz, chậm hơn khoảng 1000 lần so với kiểm tra enum. (Ứng dụng của chúng tôi có thời hạn vòng lặp chính là 11,1 mili giây, vì vậy chúng tôi quan tâm đến micro giây rất nhiều.)
Crashworks

6
Hiệu suất khác nhau rất nhiều giữa các lần triển khai. GCC sử dụng so sánh con trỏ typeinfo nhanh chóng. MSVC sử dụng so sánh chuỗi không nhanh. Tuy nhiên, phương thức của MSVC sẽ hoạt động với mã được liên kết với các phiên bản thư viện khác nhau, tĩnh hoặc DLL, trong đó phương thức con trỏ của GCC tin rằng một lớp trong thư viện tĩnh khác với một lớp trong thư viện chia sẻ.
Zan Lynx

1
@Crashworks Chỉ cần có một bản ghi đầy đủ ở đây: đó là trình biên dịch nào (và phiên bản nào)?
H. Guijt

@Crashworks chấp nhận yêu cầu cung cấp thông tin mà trình biên dịch đã tạo ra kết quả quan sát của bạn; cảm ơn.
underscore_d

@underscore_d: MSVC.
Crashworks

9

C ++ được xây dựng dựa trên ý tưởng kiểm tra kiểu tĩnh.

[1] RTTI, có nghĩa là, dynamic_casttype_id, là kiểm tra kiểu động.

Vì vậy, về cơ bản bạn đang hỏi tại sao kiểm tra kiểu tĩnh lại thích hợp hơn kiểm tra kiểu động. Và câu trả lời đơn giản là, liệu kiểm tra kiểu tĩnh có thích hợp hơn kiểm tra kiểu động hay không, tùy thuộc . Trên rất nhiều. Nhưng C ++ là một trong những ngôn ngữ lập trình được thiết kế xoay quanh ý tưởng kiểm tra kiểu tĩnh. Và điều này có nghĩa là quy trình phát triển, cụ thể là thử nghiệm, thường được điều chỉnh để kiểm tra kiểu tĩnh và sau đó phù hợp nhất với điều đó.


Re

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

bạn có thể thực hiện quy trình-không đồng nhất-nút-của-biểu đồ này với kiểm tra kiểu tĩnh và không truyền bất kỳ thứ gì qua mẫu khách truy cập, ví dụ như thế này:

#include <iostream>
#include <set>
#include <initializer_list>

namespace graph {
    using std::set;

    class Red_thing;
    class Yellow_thing;
    class Orange_thing;

    struct Callback
    {
        virtual void handle( Red_thing& ) {}
        virtual void handle( Yellow_thing& ) {}
        virtual void handle( Orange_thing& ) {}
    };

    class Node
    {
    private:
        set<Node*> connected_;

    public:
        virtual void call( Callback& cb ) = 0;

        void connect_to( Node* p_other )
        {
            connected_.insert( p_other );
        }

        void call_on_connected( Callback& cb )
        {
            for( auto const p : connected_ ) { p->call( cb ); }
        }

        virtual ~Node(){}
    };

    class Red_thing
        : public virtual Node
    {
    public:
        void call( Callback& cb ) override { cb.handle( *this ); }

        auto redness() -> int { return 255; }
    };

    class Yellow_thing
        : public virtual Node
    {
    public:
        void call( Callback& cb ) override { cb.handle( *this ); }
    };

    class Orange_thing
        : public Red_thing
        , public Yellow_thing
    {
    public:
        void call( Callback& cb ) override { cb.handle( *this ); }

        void poke() { std::cout << "Poked!\n"; }

        void poke_connected_orange_things()
        {
            struct Poker: Callback
            {
                void handle( Orange_thing& obj ) override
                {
                    obj.poke();
                }
            } poker;

            call_on_connected( poker );
        }
    };
}  // namespace graph

auto main() -> int
{
    using namespace graph;

    Red_thing   r;
    Yellow_thing    y1, y2;
    Orange_thing    o1, o2, o3;

    for( Node* p : std::initializer_list<Node*>{ &y1, &y2, &r, &o2, &o3 } )
    {
        o1.connect_to( p );
    }
    o1.poke_connected_orange_things();
}

Điều này giả định rằng tập hợp các loại nút đã biết.

Khi không, kiểu khách truy cập (có nhiều biến thể của nó) có thể được thể hiện bằng một vài kiểu tập trung hoặc chỉ một kiểu duy nhất.


Đối với cách tiếp cận dựa trên mẫu, hãy xem thư viện Đồ thị tăng cường. Thật buồn khi nói rằng tôi không quen với nó, tôi chưa sử dụng nó. Vì vậy, tôi không chắc chính xác nó hoạt động như thế nào và ở mức độ nào, nó sử dụng kiểm tra kiểu tĩnh thay vì RTTI, nhưng vì Boost thường dựa trên mẫu với kiểm tra kiểu tĩnh là ý tưởng trung tâm, tôi nghĩ bạn sẽ thấy rằng thư viện phụ Graph của nó cũng dựa trên việc kiểm tra kiểu tĩnh.


[1] Thông tin loại thời gian chạy .


1
Một điều "buồn cười" cần lưu ý là người ta có thể giảm số lượng mã (thay đổi khi thêm loại) cần thiết cho mẫu khách truy cập bằng cách sử dụng RTTI để "leo lên" một hệ thống phân cấp. Tôi biết đây là "mẫu khách truy cập nhanh".
Daniel Jour

3

Tất nhiên có một tình huống mà tính đa hình không thể không xảy ra: tên. typeidcho phép bạn truy cập vào tên của loại, mặc dù cách tên này được mã hóa là do việc triển khai xác định. Nhưng thông thường đây không phải là vấn đề vì bạn có thể so sánh hai typeid-s:

if ( typeid(5) == "int" )
    // may be false

if ( typeid(5) == typeid(int) )
   // always true

Điều tương tự đối với hàm băm.

[...] RTTI "được coi là có hại"

có hại chắc chắn là nói quá: RTTI có một số nhược điểm, nhưng nó cũng có lợi thế.

Bạn không thực sự phải sử dụng RTTI. RTTI là một công cụ để giải quyết các vấn đề OOP: nếu bạn sử dụng một mô hình khác, những mô hình này có thể sẽ biến mất. C không có RTTI, nhưng vẫn hoạt động. C ++ hỗ trợ thay vì hoàn toàn OOP và cung cấp cho bạn nhiều công cụ để khắc phục một số vấn đề mà có thể yêu cầu thông tin thời gian chạy: một trong số họ thực sự RTTI, mà dù đi kèm với một mức giá. Nếu bạn không đủ khả năng chi trả, điều tốt hơn hết là bạn chỉ nên nêu rõ sau khi phân tích hiệu suất an toàn, vẫn có trường phái cũ void*: nó miễn phí. Không tốn kém. Nhưng bạn không nhận được loại an toàn. Vì vậy, đó là tất cả về giao dịch.


  • Một số trình biên dịch không sử dụng / RTTI không phải lúc nào cũng được bật
    Tôi thực sự không mua đối số 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.

Nếu bạn viết (có thể nghiêm ngặt) mã C ++, bạn có thể mong đợi cùng một hành vi bất kể việc triển khai như thế nào. Các triển khai tuân thủ tiêu chuẩn sẽ hỗ trợ các tính năng C ++ tiêu chuẩn.

Nhưng hãy xem xét rằng trong một số môi trường mà C ++ định nghĩa (những môi trường «tự do»), RTTI không cần được cung cấp và cũng không có ngoại lệ, virtualv.v. RTTI cần một lớp bên dưới để hoạt động chính xác, xử lý các chi tiết cấp thấp như ABI và thông tin loại thực tế.


Tôi đồng ý với Yakk về RTTI trong trường hợp này. Có, nó có thể được sử dụng; nhưng nó có đúng về mặt logic không? Thực tế là ngôn ngữ cho phép bạn bỏ qua kiểm tra này không có nghĩa là nó nên được thực hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.