CRTP để tránh đa hình động


Câu trả lời:


139

Có hai cách.

Cách đầu tiên là chỉ định giao diện tĩnh cho cấu trúc của các loại:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo(); // required to compile.
};

struct your_type : base<your_type> {
  void foo(); // required to compile.
};

Cách thứ hai là tránh sử dụng thành ngữ tham chiếu đến cơ sở hoặc con trỏ đến cơ sở và thực hiện việc nối dây tại thời điểm biên dịch. Sử dụng định nghĩa trên, bạn có thể có các hàm mẫu trông giống như sau:

template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
  obj.foo(); // will do static dispatch
}

struct not_derived_from_base { }; // notice, not derived from base

// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload

Vì vậy, kết hợp định nghĩa cấu trúc / giao diện và loại trừ thời gian biên dịch trong các chức năng của bạn cho phép bạn thực hiện điều phối tĩnh thay vì điều phối động. Đây là bản chất của tính đa hình tĩnh.


15
Câu trả lời xuất sắc
Eli Bendersky

5
Tôi muốn nhấn mạnh rằng not_derived_from_basekhông có nguồn gốc từ base, cũng không phải là nó có nguồn gốc từ base...
leftaroundabout

3
Trên thực tế, khai báo foo () bên trong my_type / your_type là không bắt buộc. codepad.org/ylpEm1up (Gây tràn ngăn xếp) - Có cách nào để thực thi định nghĩa foo tại thời điểm biên dịch không? - Ok, đã tìm thấy một giải pháp: Ideone.com/C6Oz9 - Có thể bạn muốn sửa điều đó trong câu trả lời của mình.
cooky451 ngày

3
Bạn có thể giải thích cho tôi động lực để sử dụng CRTP trong ví dụ này là gì không? If bar sẽ được định nghĩa là template <class T> void bar (T & obj) {obj.foo (); }, thì bất kỳ lớp nào cung cấp foo sẽ ổn. Vì vậy, dựa trên ví dụ của bạn, có vẻ như mục đích sử dụng duy nhất của CRTP là chỉ định giao diện tại thời điểm biên dịch. Đó là nó để làm gì?
Anton Daneyko

1
@Dean Michael Thật vậy, mã trong ví dụ biên dịch ngay cả khi foo không được xác định trong my_type và your_type. Nếu không có những ghi đè đó, base :: foo được gọi đệ quy (và stackoverflows). Vì vậy, có thể bạn muốn sửa lại bạn trả lời như cooky451 đã hiển thị?
Anton Daneyko

18

Tôi đã tự mình tìm kiếm các cuộc thảo luận về CRTP. Todd Veldhuizen's Techniques for Scientific C ++ là một nguồn tài nguyên tuyệt vời cho điều này (1.3) và nhiều kỹ thuật nâng cao khác như các mẫu biểu thức.

Ngoài ra, tôi thấy rằng bạn có thể đọc hầu hết các bài báo gốc về C ++ Gems của Coplien trên Google books. Có lẽ vẫn vậy thôi.


@fizzer Tôi đã đọc phần bạn gợi ý, nhưng vẫn không hiểu mẫu <class T_leaftype> double sum (Matrix <T_leaftype> & A) là gì; mua cho bạn so với mẫu <class Anything> double sum (Dù & A);
Anton Daneyko

@AntonDaneyko Khi được gọi trên một cá thể cơ sở, tổng của lớp cơ sở được gọi, ví dụ: "diện tích của một hình dạng" với việc triển khai mặc định như thể nó là một hình vuông. Mục tiêu của CRTP trong trường hợp này là giải quyết việc triển khai có nguồn gốc nhiều nhất, "diện tích hình thang", v.v. trong khi vẫn có thể tham chiếu đến hình thang như một hình dạng cho đến khi hành vi dẫn xuất được yêu cầu. Về cơ bản, bất cứ khi nào bạn thường cần dynamic_casthoặc các phương thức ảo.
John P

1

Tôi đã phải tra cứu CRTP . Tuy nhiên, sau khi làm điều đó, tôi đã tìm thấy một số thông tin về Tính đa hình tĩnh . Tôi nghi ngờ rằng đây là câu trả lời cho câu hỏi của bạn.

Nó chỉ ra rằng ATL sử dụng mô hình này khá rộng rãi.


-5

Đây câu trả lời Wikipedia có mọi thứ bạn cần. Cụ thể:

template <class Derived> struct Base
{
    void interface()
    {
        // ...
        static_cast<Derived*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        Derived::static_sub_func();
        // ...
    }
};

struct Derived : Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

Mặc dù tôi không biết điều này thực sự mua bạn bao nhiêu. Chi phí của một lệnh gọi hàm ảo là (tất nhiên là phụ thuộc vào trình biên dịch):

  • Bộ nhớ: Một con trỏ chức năng cho mỗi chức năng ảo
  • Runtime: Một cuộc gọi con trỏ hàm

Trong khi tổng chi phí của đa hình tĩnh CRTP là:

  • Bộ nhớ: Bản sao của Base trên mỗi mẫu khởi tạo
  • Thời gian chạy: Một lệnh gọi con trỏ hàm + bất cứ điều gì static_cast đang làm

4
Trên thực tế, việc sao chép Base per template tức thời là một ảo tưởng vì (trừ khi bạn vẫn có vtable) trình biên dịch sẽ hợp nhất lưu trữ của base và dẫn xuất thành một cấu trúc duy nhất cho bạn. Lời gọi con trỏ hàm cũng được trình biên dịch tối ưu hóa (phần static_cast).
Dean Michael

19
Nhân tiện, phân tích CRTP của bạn không chính xác. Nó nên là: Trí nhớ: Không có gì, như Dean Michael đã nói. Runtime: Một lệnh gọi hàm tĩnh (nhanh hơn), không phải ảo, là toàn bộ điểm của bài tập. static_cast không làm gì cả, nó chỉ cho phép mã biên dịch.
Frederik Slijkerman,

2
Quan điểm của tôi là mã cơ sở sẽ được sao chép trong tất cả các trường hợp mẫu (chính là sự hợp nhất mà bạn nói đến). Giống như việc có một mẫu chỉ với một phương thức dựa vào tham số mẫu; mọi thứ khác tốt hơn trong một lớp cơ sở nếu không nó được kéo vào ('hợp nhất') nhiều lần.
user23167,

1
Mỗi phương thức trong cơ sở sẽ được biên dịch lại cho mỗi dẫn xuất. Trong trường hợp (dự kiến) khi mỗi phương thức khởi tạo là khác nhau (vì các thuộc tính của Derived là khác nhau), điều đó không nhất thiết phải được tính là chi phí. Nhưng nó có thể dẫn đến kích thước mã tổng thể lớn hơn, so với trường hợp một phương thức phức tạp trong lớp cơ sở (bình thường) gọi các phương thức ảo của lớp con. Ngoài ra, nếu bạn đặt các phương thức tiện ích trong Base <Derived>, không thực sự phụ thuộc vào <Derived>, chúng sẽ vẫn được khởi tạo. Có thể tối ưu hóa toàn cầu sẽ khắc phục phần nào điều đó.
greggo

Một cuộc gọi đi qua một số lớp CRTP sẽ mở rộng trong bộ nhớ trong quá trình biên dịch nhưng có thể dễ dàng thu gọn thông qua TCO và nội tuyến. CRTP chính nó không thực sự là thủ phạm, phải không?
John P
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.