2009-08-03 41 views
30

tôi có vấn đề .. tôi không hiểu Bản mẫu Lập trình meta.Mẫu Lập trình meta - tôi vẫn không hiểu: (

Vấn đề là: tôi đã đọc rất nhiều. tôi:/

Fact Nr.1 ​​: Mẫu Metaprogramming là nhanh

template <int N> 
struct Factorial 
{ 
    enum { value = N * Factorial<N - 1>::value }; 
}; 

template <> 
struct Factorial<0> 
{ 
    enum { value = 1 }; 
}; 

// Factorial<4>::value == 24 
// Factorial<0>::value == 1 
void foo() 
{ 
    int x = Factorial<4>::value; // == 24 
    int y = Factorial<0>::value; // == 1 
} 

nên metaprogram này là nhanh hơn ... + Nhà của Literal liên tục
. NHƯNG: Ở đâu trong Thế giới thực chúng ta có các hằng số?
Hầu hết các chương trình tôi sử dụng phản ứng trên đầu vào của người dùng.

FACT nr. 2: Bản mẫu Lập trình meta có thể hoàn thành khả năng bảo trì tốt hơn.

Yeah.Ví dụ Factorial Có thể duy trì ... nhưng khi nói đến các hàm phức tạp, tôi và hầu hết các lập trình viên C++ khác không thể đọc các hàm. Và các tùy chọn gỡ lỗi kém (hoặc ít nhất tôi không biết cách gỡ lỗi)

Trường hợp lập trình mẫu có ý nghĩa gì?

+3

Nó không bao giờ có ý nghĩa với tôi ... – Kawa

+10

Đừng cảm thấy xấu, hầu như không ai nhận được nó. –

Trả lời

26

Cũng như giai thừa không phải là một ví dụ thực tế về đệ quy trong các ngôn ngữ không có chức năng, không phải là một ví dụ thực tế về lập trình meta mẫu. Nó chỉ là ví dụ tiêu chuẩn mà mọi người tiếp cận khi họ muốn cho bạn thấy sự đệ quy.

Khi viết mẫu cho các mục đích thực tế, chẳng hạn như trong thư viện hàng ngày, thường mẫu phải phù hợp với những gì nó tùy thuộc vào thông số loại được khởi tạo bằng. Điều này có thể khá phức tạp, vì mẫu có hiệu quả lựa chọn mã để tạo ra, có điều kiện. Đây là những gì metaprogramming mẫu là; nếu khuôn mẫu phải lặp (thông qua đệ quy) và chọn giữa các lựa chọn thay thế, nó có hiệu quả giống như một chương trình nhỏ thực hiện trong quá trình biên dịch để tạo mã đúng.

Đây là hướng dẫn thực sự tốt đẹp từ các trang tài liệu tăng cường (thực sự được trích từ một số brilliant book, đáng đọc).

http://www.boost.org/doc/libs/1_39_0/libs/mpl/doc/tutorial/representing-dimensions.html

+3

Câu trả lời hay. +1 - Tôi muốn mọi người đã đồng ý về một ví dụ tiêu chuẩn tốt hơn. Nói chung, việc sử dụng lập trình meta để tính giá trị chỉ là vô nghĩa. Đó là khai sáng như là một bài tập học tập, khi cố gắng tìm ra TMP, nhưng không phải là một trường hợp sử dụng, và không phải là một điểm bán hàng để thuyết phục mọi người về tính năng tuyệt vời như thế nào. – jalf

+1

Tôi sẽ không viết ra giá trị hoàn toàn :) http://stackoverflow.com/questions/699781/c-binary-constant-literal –

+0

Tất nhiên nó có thể hữu ích, nhưng nó không thực sự là điểm bán hàng lớn để thuyết phục mọi người rằng tính năng này đáng giá. Ví dụ nhị phân là tốt hơn bình thường, nhưng vẫn còn, câu hỏi rõ ràng là "tại sao không chỉ làm điều đó trong thời gian chạy? Nó không giống như nó cực kỳ tốn kém." Điều đó có thực sự biện minh cho việc phân lớp một ngôn ngữ hoàn chỉnh khác trên đầu trang của C++ không? ;) – jalf

2

Tôi đề nghị bạn đọc Modern C++ Design bởi Andrei Alexandrescu - đây có lẽ là một trong những cuốn sách hay nhất về sử dụng thực tế của lập trình meta mẫu C++; và mô tả nhiều vấn đề mà các mẫu C++ là một giải pháp tuyệt vời.

+14

Mọi nhà phát triển C++ nên đọc cuốn sách đó, nhưng không phải tất cả đều hữu ích khi trả lời câu hỏi, vì cùng một lý do đơn giản là việc cung cấp liên kết tới Google thường bị cau mày. SO được cho là một nơi để tìm câu trả lời, chứ không phải liên kết đến các tài nguyên khác mà bạn có thể thử tìm kiếm thay thế. – jalf

+0

Phiên bản thứ 3 của Hiệu ứng C++ của Scott Meyers cũng có một ví dụ được giải thích khá kỹ lưỡng. (Mặc dù đó là về chức năng quá tải thay vì chức năng meta và do đó có thể không có vẻ như là "TMP thực" lúc đầu.) – sbi

2

TMP có thể được sử dụng từ bất cứ điều gì như đảm bảo tính chính xác chiều (Đảm bảo khối lượng mà không thể được chia theo thời gian, nhưng khoảng cách có thể được chia theo thời gian và được gán cho một biến vận tốc) để tối ưu hóa hoạt động ma trận bằng cách loại bỏ tạm thời các đối tượng và các vòng kết hợp khi có nhiều ma trận tham gia.

+0

khối lượng có thể được chia theo thời gian. Kết quả chỉ phải là loại "khối lượng mỗi lần";) Nhưng yeah, TMP có thể được sử dụng để thực thi điều đó. – jalf

+0

Bất cứ điều gì có thể được chia/nhân với bất cứ điều gì, đó là cộng/trừ đi các chuyến đi của mọi người. –

3

Scott Meyers đã làm việc để thực thi ràng buộc mã bằng cách sử dụng TMP.

của nó khá tốt đọc:
http://www.artima.com/cppsource/codefeatures.html

Trong bài viết này, ông giới thiệu các khái niệm về Bộ các loại (không phải là một khái niệm mới nhưng tác phẩm của ông được dựa ontop của khái niệm này). Sau đó sử dụng TMP để đảm bảo rằng không có vấn đề gì thứ tự bạn chỉ định các thành viên của tập hợp rằng nếu hai bộ được thực hiện của các thành viên tương tự thì họ là equavalent. Điều này đòi hỏi rằng anh ta có thể sắp xếp và sắp xếp lại một danh sách các loại và so sánh chúng một cách động, do đó tạo ra các lỗi thời gian biên dịch khi chúng không khớp.

+0

Thực ra tên của ông là "Meyers". (Xin lỗi vì đã chơi nitpicking.) – sbi

+0

Đã sửa. :-) –

9

để Metaprogram này nhanh hơn ... beacause của Constant Literal. NHƯNG: Ở đâu trong thế giới thực, chúng ta có các hằng số? Hầu hết các chương trình tôi sử dụng phản ứng trên đầu vào của người dùng.

Đó là lý do tại sao nó hầu như không bao giờ được sử dụng cho các giá trị. Thông thường, nó được sử dụng trên các loại. sử dụng các loại để tính toán và tạo các loại mới.

Có nhiều ứng dụng trong thế giới thực, một số trong số đó bạn đã quen thuộc ngay cả khi bạn không nhận ra nó.

Một trong những ví dụ yêu thích của tôi là các trình lặp. Chúng chủ yếu được thiết kế chỉ với lập trình chung, có, nhưng lập trình meta mẫu là hữu ích ở một nơi cụ thể:

Để vá lỗi con trỏ để chúng có thể được sử dụng làm trình lặp. Trình lặp phải hiển thị một số kiểu chữ, chẳng hạn như value_type. Con trỏ không làm điều đó.

Vì vậy, mã như sau (về cơ bản giống với những gì bạn tìm thấy trong Boost.Iterator)

template <typename T> 
struct value_type { 
    typedef typename T::value_type type; 
}; 

template <typename T> 
struct value_type<T*> { 
    typedef T type; 
}; 

là một mẫu metaprogram rất đơn giản, nhưng đó là rất hữu ích. Nó cho phép bạn có được kiểu giá trị của bất kỳ loại biến lặp T nào, cho dù đó là một con trỏ hay một lớp, chỉ đơn giản là bởi value_type<T>::type.

Và tôi nghĩ rằng ở trên có một số lợi ích rất rõ ràng khi nói đến khả năng bảo trì. Thuật toán của bạn hoạt động trên các trình vòng lặp chỉ phải được thực hiện một lần. Nếu không có thủ thuật này, bạn phải thực hiện một triển khai cho con trỏ và một cho các trình vòng lặp dựa trên lớp "thích hợp".

Các mẹo như boost::enable_if cũng rất có giá trị. Bạn có một quá tải của một chức năng mà nên được kích hoạt cho một tập hợp cụ thể của các loại chỉ. Thay vì xác định tình trạng quá tải cho từng loại, bạn có thể sử dụng lập trình meta để chỉ định điều kiện và chuyển nó đến enable_if.

Earwicker đã đề cập một ví dụ hay khác, một khuôn khổ để thể hiện đơn vị vật lý và kích thước. Nó cho phép bạn thể hiện tính toán như với các đơn vị vật lý được đính kèm và thực thi loại kết quả. Nhân mét theo mét tạo ra một số mét vuông. Lập trình meta mẫu có thể được sử dụng để tự động tạo đúng loại. Tuy nhiên, hầu hết thời gian, lập trình meta mẫu được sử dụng (và hữu ích) trong các trường hợp nhỏ, cách ly, về cơ bản để làm mịn các va chạm và các trường hợp ngoại lệ, để làm cho một tập hợp các kiểu nhìn và hành động thống nhất, cho phép bạn sử dụng lập trình chung hiệu quả hơn

8

Thứ hai đề xuất cho thiết kế hiện đại của Alexandrescu Thiết kế C++ hiện đại.

Mẫu thực sự tỏa sáng khi bạn đang viết một thư viện có các phần có thể được ghép lại theo cách "chọn một Foo, một quán Bar và Baz" và bạn mong đợi người dùng sử dụng các phần này dưới một số hình thức được cố định tại thời gian biên dịch. Ví dụ, tôi đồng tác giả một thư viện khai phá dữ liệu sử dụng mẫu lập trình meta để cho lập trình viên quyết định những gì DecisionType để sử dụng (phân loại, xếp hạng hoặc hồi quy), những gì InputType để mong đợi (phao, ints, liệt kê các giá trị, bất cứ điều gì), và những gì KernelMethod (đó là một điều khai thác dữ liệu). Sau đó, chúng tôi đã triển khai một số lớp khác nhau cho từng danh mục, chẳng hạn như có vài chục kết hợp có thể.

Việc triển khai 60 lớp riêng biệt để thực hiện việc này sẽ liên quan đến rất nhiều sự sao chép mã khó chịu, khó bảo trì. Lập trình meta mẫu có nghĩa là chúng tôi có thể triển khai từng khái niệm làm đơn vị mã và cung cấp cho lập trình viên một giao diện đơn giản để tạo các kết hợp của các khái niệm này tại thời gian biên dịch.

Phân tích theo chiều cũng là một ví dụ tuyệt vời, nhưng những người khác đã đề cập đến điều đó.

Tôi cũng từng viết một số trình tạo số giả ngẫu nhiên biên dịch đơn giản chỉ để gây rối với đầu người, nhưng điều đó không thực sự đếm IMO.

+5

Lộn xộn với người đứng đầu của người dân là một trong những cách sử dụng tốt nhất mà lập trình có thể được đặt! Đừng đánh giá thấp giá trị của điều đó. : p – jalf

15

Tôi sử dụng lập trình mete mẫu cho các nhà khai thác đang hoạt động của SSE để tối ưu hóa các lần xáo trộn trong suốt thời gian biên dịch.

swizzles SSE ('shuffles') chỉ có thể được đeo mặt nạ như một byte đen (giá trị ngay lập tức), vì vậy chúng tôi đã tạo ra một 'mặt nạ sáp nhập' lớp mẫu mà kết hợp mặt nạ trong thời gian biên dịch cho khi nhiều xáo trộn xảy ra:

template <unsigned target, unsigned mask> 
struct _mask_merger 
{ 
    enum 
    { 
     ROW0 = ((target >> (((mask >> 0) & 3) << 1)) & 3) << 0, 
     ROW1 = ((target >> (((mask >> 2) & 3) << 1)) & 3) << 2, 
     ROW2 = ((target >> (((mask >> 4) & 3) << 1)) & 3) << 4, 
     ROW3 = ((target >> (((mask >> 6) & 3) << 1)) & 3) << 6, 

     MASK = ROW0 | ROW1 | ROW2 | ROW3, 
    }; 
}; 

Tính năng này hoạt động và tạo ra mã đáng chú ý mà không cần tạo mã trên đầu và thời gian biên dịch nhỏ hơn.

3

Dưới đây là một ví dụ nhỏ, một chuyển đổi liên tục nhị phân, từ một câu hỏi trước đây trên StackOverflow:

C++ binary constant/literal

template< unsigned long long N > 
struct binary 
{ 
    enum { value = (N % 10) + 2 * binary<N/10> :: value } ; 
}; 
template<> 
struct binary<0> 
{ 
    enum { value = 0 } ; 
}; 
5

Ví dụ thừa là về như hữu ích cho thực tế TMP là "Xin chào, thế giới! " là dành cho lập trình chung: Nó có thể cho bạn thấy một vài kỹ thuật hữu ích (đệ quy thay vì lặp lại, "else-if-then" vv) trong một ví dụ rất đơn giản, dễ hiểu mà không có nhiều sự liên quan cho mọi -ngày mã hóa. (Khi là lần cuối cùng bạn cần viết một chương trình mà phát ra "Hello, world"?)

TMP là về thực hiện các thuật toán tại thời gian biên dịch và điều này hàm ý một vài lợi thế rõ ràng:

  • Vì các thuật toán không có nghĩa là mã của bạn không biên dịch, các thuật toán thất bại không bao giờ làm cho nó trở thành khách hàng của bạn và do đó không thể thất bại tại khách hàng. Đối với tôi, trong thập kỷ qua, đây là lợi thế quan trọng nhất giúp tôi giới thiệu TMP vào mã của các công ty mà tôi đã làm việc.
  • Vì kết quả thực hiện các chương trình mẫu-meta là mã thông thường sau đó được biên dịch bởi trình biên dịch, tất cả các ưu điểm của thuật toán tạo mã (giảm dự phòng, v.v.) được áp dụng.
  • Tất nhiên, vì chúng được thực hiện tại thời gian biên dịch, các thuật toán này sẽ không cần bất kỳ thời gian chạy nào và do đó sẽ chạy nhanh hơn.TMP chủ yếu là về tính toán thời gian biên dịch với một số ít, chủ yếu là các hàm nhỏ, nội tuyến được rải ở giữa, do đó các trình biên dịch có nhiều cơ hội để tối ưu hóa mã kết quả.

Tất nhiên, có nhược điểm, quá:

  • Các thông báo lỗi có thể khủng khiếp.
  • Không có gỡ lỗi.
  • Mã thường khó đọc.

Như mọi khi, bạn sẽ chỉ phải cân nhắc những lợi thế so với những bất lợi trong mọi trường hợp.

Đối với một hữu ích hơn dụ: Một khi bạn đã nắm được danh sách loại và các thuật toán cơ bản thời gian biên dịch hoạt động trên đó, bạn có thể hiểu như sau:

typedef 
    type_list_generator< signed char 
         , signed short 
         , signed int 
         , signed long 
         >::result_type 
    signed_int_type_list; 

typedef 
    type_list_find_if< signed_int_type_list 
        , exact_size_predicate<8> 
        >::result_type 
    int8_t; 

typedef 
    type_list_find_if< signed_int_type_list 
        , exact_size_predicate<16> 
        >::result_type 
    int16_t; 

typedef 
    type_list_find_if< signed_int_type_list 
        , exact_size_predicate<32> 
        >::result_type 
    int32_t; 

này (hơi đơn giản) mã thực tế Tôi đã viết cách đây vài tuần. Nó sẽ chọn các loại thích hợp từ danh sách loại, thay thế các tên truy nhập #ifdef phổ biến trong mã di động. Nó không cần bảo trì, hoạt động mà không có sự thích ứng trên mọi nền tảng mà mã của bạn có thể cần được chuyển đến và phát ra lỗi biên dịch nếu nền tảng hiện tại không có đúng loại.

Một ví dụ khác là thế này:

template< typename TFunc, typename TFwdIter > 
typename func_traits<TFunc>::result_t callFunc(TFunc f, TFwdIter begin, TFwdIter end); 

Với một hàm f và một chuỗi các chuỗi, điều này sẽ phân tích chữ ký của chức năng, chuyển đổi chuỗi từ chuỗi thành các loại đúng, và gọi hàm với những các đối tượng. Và nó chủ yếu là TMP bên trong.

3

TMP không nhất thiết có nghĩa là mã nhanh hơn hoặc dễ bảo trì hơn. Tôi đã sử dụng thư viện tăng cường spirit để triển khai thực hiện một trình phân tích cú pháp biểu thức SQL đơn giản, xây dựng một cấu trúc cây đánh giá. Trong khi thời gian phát triển bị giảm vì tôi đã quen với TMP và lambda, đường cong học tập là một bức tường gạch cho các nhà phát triển "C với các lớp", và hiệu suất không tốt bằng LEX/YACC truyền thống.

Tôi thấy Lập trình mẫu tiêu bản như một công cụ khác trong vành đai công cụ của mình. Khi nó hoạt động để bạn sử dụng nó, nếu không, hãy sử dụng một công cụ khác. Giá trị

2

'giá trị const tĩnh' cũng hoạt động. Và con trỏ đến thành viên. Và đừng quên thế giới của các loại (rõ ràng và suy luận) như các đối số thời gian biên dịch!

NHƯNG: Ở đâu trong thế giới thực chúng ta có các hằng số?

Giả sử bạn có một số mã phải chạy nhanh nhất có thể. Nó chứa vòng lặp quan trọng bên trong của tính toán CPU ràng buộc của bạn trong thực tế. Bạn sẽ sẵn sàng tăng kích thước tệp thực thi của mình một chút để làm cho nó nhanh hơn. Có vẻ như:

double innerLoop(const bool b, const vector<double> & v) 
{ 
    // some logic involving b 

    for (vector::const_iterator it = v.begin; it != v.end(); ++it) 
    { 
     // significant logic involving b 
    } 

    // more logic involving b 
    return .... 
} 

Chi tiết không quan trọng, nhưng việc sử dụng 'b' phổ biến trong quá trình triển khai.

Bây giờ, với các mẫu, bạn có thể cấu trúc lại nó một chút:

template <bool b> double innerLoop_B(vector<double> v) { ... same as before ... } 
double innerLoop(const bool b, const vector<double> & v) 
{ return b ? innerLoop_templ_B<true>(v) : innerLoop_templ_B<false>(v)); } 

Bất cứ lúc nào bạn có một tương đối nhỏ, rời rạc, thiết lập các giá trị cho một tham số bạn có thể phiên bản tự động thuyết minh riêng biệt cho họ.

Cân nhắc các khả năng khi 'b' dựa trên phát hiện CPU. Bạn có thể chạy một bộ mã được tối ưu hóa khác nhau tùy thuộc vào việc phát hiện thời gian chạy. Tất cả từ cùng một mã nguồn, hoặc bạn có thể chuyên một số chức năng cho một số bộ giá trị.

Ví dụ cụ thể, tôi đã từng thấy một số mã cần thiết để hợp nhất một số tọa độ nguyên. Hệ tọa độ 'a' là một trong hai độ phân giải (được biết ở thời gian biên dịch), và hệ tọa độ 'b' là một trong hai độ phân giải khác nhau (cũng được biết ở thời gian biên dịch). Hệ tọa độ đích cần phải là bội số chung nhỏ nhất của hai hệ tọa độ nguồn. Một thư viện được sử dụng để tính toán LCM tại thời gian biên dịch và tạo mã cho các khả năng khác nhau.

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