2012-08-04 48 views
35

Hãy nhìn vào một lợi ích cụ thể của các mẫu biểu: ETS có thể được sử dụng để tránh temporaries vector có kích thước trong bộ nhớ xảy ra trong các nhà khai thác quá tải như:mẫu biểu và C++ 11

template<typename T> 
std::vector<T> operator+(const std::vector<T>& a, const std::vector<T>& b) 
{ 
    std::vector<T> tmp; // vector-sized temporary 
    for_each(...); 
    return tmp; 
} 

Trong C++ 11 câu lệnh return của hàm này áp dụng các ngữ nghĩa di chuyển. Không có bản sao của vectơ. Đó là một chiến thắng.

Tuy nhiên, nếu tôi nhìn vào một biểu thức đơn giản như

d = a + b + c; 

Tôi thấy rằng các chức năng trên được gọi hai lần (cho cả operator+) trong khi việc chuyển nhượng cuối cùng có thể được thực hiện với ngữ nghĩa di chuyển.

Tổng cộng 2 vòng được thực hiện. Có nghĩa là tôi tạm thời đọc và đọc lại nó ngay sau đó. Đối với các vectơ lớn, điều này rơi ra khỏi bộ nhớ cache. Đó là tồi tệ hơn so với các mẫu biểu hiện. Họ có thể làm toàn bộ điều chỉ trong vòng 1. ETS có thể thực thi mã tương đương ở trên để:

for(int i=0 ; i < vec_length ; ++i) 
    d[i] = a[i] + b[i] + c[i]; 

tôi đã tự hỏi liệu lambdas cùng với ngữ nghĩa di chuyển hoặc bất kỳ tính năng mới khác có thể làm tốt như ETS. Có suy nghĩ gì không?

Edit:

Về cơ bản, sử dụng kỹ thuật ET trình biên dịch xây dựng một cây phân tích cú pháp tương tự như khái niệm đại số với nó hệ thống kiểu. Cây này bao gồm các nút bên trong và các nút lá. Các nút bên trong thể hiện các phép toán (phép cộng, phép nhân, v.v.) và các nút lá đại diện cho các tham chiếu đến các đối tượng dữ liệu.

Tôi đã cố gắng nghĩ về toàn bộ quá trình tính toán trong thời trang của máy xếp chồng : Thực hiện thao tác từ ngăn xếp hoạt động và kéo các đối số kế tiếp từ ngăn xếp đối số và đánh giá hoạt động. Đặt kết quả trở lại trên ngăn xếp đang chờ thao tác.

Đại diện cho hai đối tượng khác nhau (stack hoạt động và dữ liệu lá stack) Tôi đi kèm với nhau một std::tuple cho các hoạt động và một std::tuple cho các dữ liệu rời thành một std::pair<>. Ban đầu tôi đã sử dụng số std:vector nhưng dẫn đến chi phí thời gian chạy.

Toàn bộ quá trình được thực hiện theo hai giai đoạn: Khởi tạo máy ngăn xếp nơi ngăn xếp hoạt động và đối số được khởi tạo. Và giai đoạn đánh giá được kích hoạt bằng cách gán các vùng chứa được ghép nối cho vectơ.

Tôi tạo ra một lớp Vec nắm giữ một tin array<int,5> (các payload) và các tính năng mà một nhà điều hành phân công quá tải mà mất các "khái niệm".

Các toàn cầu operator* bị quá tải cho tất cả các kết hợp của việc Vec và "khái niệm" để cho phép việc xử lý đúng cũng trong trường hợp nơi chúng tôi có nhiều hơn chỉ là a*b.(Lưu ý, tôi chuyển sang cho dụ giáo dục này cho phép nhân - về cơ bản để nhanh chóng phát các imull trong lắp ráp.)

gì được thực hiện đầu tiên trước khi đánh giá bắt đầu được "chiết xuất" các giá trị ra khỏi tham gia Vec đối tượng và khởi tạo đối số ngăn xếp. Điều đó là cần thiết để không có các loại đối tượng khác nhau nằm ở xung quanh: Các vectơ có thể lập chỉ mục và các kết quả không thể lập chỉ mục. Đây là nội dung của Extractor. Điều tốt đẹp một lần nữa: Variadic mẫu được sử dụng mà trong trường hợp này kết quả trong không có thời gian chạy trên không (tất cả điều này được thực hiện tại thời gian biên dịch).

Toàn bộ điều hoạt động. Biểu thức được đánh giá độc đáo (tôi cũng đã thêm phần bổ sung, nhưng điều đó được bỏ lại ở đây để phù hợp với mã). Bên dưới bạn có thể thấy đầu ra của bộ kết hợp. Chỉ cần định nghĩa thô, chính xác như bạn muốn nó là: En-par với kỹ thuật ET.

Ảnh chụp nhanh. Các tính năng ngôn ngữ mới của C++ 11 cung cấp các mẫu variadic (cùng với siêu lập trình mẫu) mở ra khu vực tính toán thời gian biên dịch. Tôi đã chỉ ra ở đây cách các lợi ích của Các mẫu có thể được sử dụng để tạo mã tốt như với kỹ thuật truyền thống ET .

#include<algorithm> 
#include<iostream> 
#include<vector> 
#include<tuple> 
#include<utility> 
#include<array> 



template<typename Target,typename Tuple, int N, bool end> 
struct Extractor { 
    template < typename ... Args > 
    static Target index(int i,const Tuple& t, Args && ... args) 
    { 
    return Extractor<Target, Tuple, N+1, 
      std::tuple_size<Tuple>::value == N+1>:: 
     index(i, t , std::forward<Args>(args)..., std::get<N>(t).vec[i]); 
    } 
}; 

template < typename Target, typename Tuple, int N > 
struct Extractor<Target,Tuple,N,true> 
{ 
    template < typename ... Args > 
    static Target index(int i,Tuple const& t, 
      Args && ... args) { 
     return Target(std::forward<Args>(args)...); } 
}; 

template < typename ... Vs > 
std::tuple<typename std::remove_reference<Vs>::type::type_t...> 
extract(int i , const std::tuple<Vs...>& tpl) 
{ 
    return Extractor<std::tuple<typename std::remove_reference<Vs>::type::type_t...>, 
      std::tuple<Vs...>, 0, 
      std::tuple_size<std::tuple<Vs...> >::value == 0>::index(i,tpl); 
} 


struct Vec { 
    std::array<int,5> vec; 
    typedef int type_t; 

    template<typename... OPs,typename... VALs> 
    Vec& operator=(const std::pair< std::tuple<VALs...> , std::tuple<OPs...> >& e) { 
    for(int i = 0 ; i < vec.size() ; ++i) { 
     vec[i] = eval(extract(i,e.first) , e.second); 
    } 
    } 
}; 




template<int OpPos,int ValPos, bool end> 
struct StackMachine { 
    template<typename... OPs,typename... VALs> 
    static void eval_pos(std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops) 
    { 
    std::get<ValPos+1>(vals) = 
     std::get<OpPos>(ops).apply(std::get<ValPos>(vals) , 
        std::get<ValPos+1>(vals)); 
    StackMachine<OpPos+1,ValPos+1,sizeof...(OPs) == OpPos+1>::eval_pos(vals,ops); 
    } 
}; 

template<int OpPos,int ValPos> 
struct StackMachine<OpPos,ValPos,true> { 
    template<typename... OPs,typename... VALs> 
    static void eval_pos(std::tuple<VALs...>& vals , 
      const std::tuple<OPs...> & ops) 
    {} 
}; 



template<typename... OPs,typename... VALs> 
int eval(const std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops) 
{ 
    StackMachine<0,0,false>::eval_pos(const_cast<std::tuple<VALs...>&>(vals),ops); 
    return std::get<sizeof...(OPs)>(vals); 
} 




struct OpMul { 
    static int apply(const int& lhs,const int& rhs) { 
    return lhs*rhs; 
    } 
}; 

std::pair< std::tuple< const Vec&, const Vec& > , std::tuple<OpMul> > 
operator*(const Vec& lhs,const Vec& rhs) 
{ 
    return std::make_pair(std::tuple< const Vec&, const Vec& >(lhs , rhs) , 
      std::tuple<OpMul>(OpMul())); 
} 

template<typename... OPs,typename... VALs> 
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> > 
operator*(const Vec& lhs,const std::pair< std::tuple<VALs...> , std::tuple<OPs...> >& rhs) 
{ 
    return std::make_pair(std::tuple_cat(rhs.first , std::tuple< const Vec& >(lhs) ) , 
      std::tuple_cat(rhs.second , std::tuple<OpMul>(OpMul()) )); 
} 

template<typename... OPs,typename... VALs> 
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> > 
operator*(const std::pair< std::tuple<VALs...> , std::tuple<OPs...> >& lhs, 
     const Vec& rhs) 
{ 
    return std::make_pair(std::tuple_cat(lhs.first , std::tuple< const Vec& >(rhs) ) , 
      std::tuple_cat(lhs.second , std::tuple<OpMul>(OpMul()))); 
} 

int main() 
{ 
    Vec d,c,b,a; 


    for(int i = 0 ; i < d.vec.size() ; ++i) { 
    a.vec[i] = 10+i; 
    b.vec[i] = 20+i; 
    c.vec[i] = 30+i; 
    d.vec[i] = 0; 
    } 

    d = a * b * c * a; 

    for(int i = 0 ; i < d.vec.size() ; ++i) 
    std::cout << d.vec[i] << std::endl; 
} 

Assembler được tạo ra với g++-4.6 -O3 (tôi đã phải đặt một số phụ thuộc thời gian chạy vào khởi tạo vector để trình biên dịch không tính toán toàn bộ điều tại thời gian biên dịch và bạn thực sự thấy imull instaructions.)

imull %esi, %edx 
imull 32(%rsp), %edx 
imull %edx, %esi 
movl 68(%rsp), %edx 
imull %ecx, %edx 
movl %esi, (%rsp) 
imull 36(%rsp), %edx 
imull %ecx, %edx 
movl 104(%rsp), %ecx 
movl %edx, 4(%rsp) 
movl 72(%rsp), %edx 
imull %ecx, %edx 
imull 40(%rsp), %edx 
imull %ecx, %edx 
movl 108(%rsp), %ecx 
movl %edx, 8(%rsp) 
movl 76(%rsp), %edx 
imull %ecx, %edx 
imull 44(%rsp), %edx 
imull %ecx, %edx 
movl 112(%rsp), %ecx 
movl %edx, 12(%rsp) 
movl 80(%rsp), %edx 
imull %ecx, %edx 
imull %eax, %edx 
imull %ecx, %edx 
movl %edx, 16(%rsp) 
+3

Bạn nên tra cứu [copy elision and RVO] (http://en.wikipedia.org/wiki/Copy_elision). Ngoài ra, chuyển một trong các vectơ theo giá trị thay vì tạo bản sao 'tmp' của riêng bạn có thể hữu ích. – juanchopanza

+0

Đi qua giá trị không thể trợ giúp (bản sao vẫn được tạo). (N) RVO không giúp loại bỏ vòng lặp bổ sung – ritter

+0

@Frank: bạn có chắc chắn không? Đây là một ứng cử viên mạnh mẽ cho RVO. – akappa

Trả lời

45

Tôi đã tự hỏi liệu lambdas cùng với ngữ nghĩa di chuyển hay bất kỳ tính năng mới nào khác có thể hoạt động tốt như ET. Có suy nghĩ gì không?

nhanh trả lời

Move ngữ nghĩa không phải là thuốc chữa bách bệnh tổng trên --techniques riêng của họ chẳng hạn như mẫu biểu thức (ETS) vẫn cần thiết trong C++ 11 để loại bỏ các chi phí như di chuyển dữ liệu xung quanh ! Vì vậy, để trả lời câu hỏi của bạn một cách nhanh chóng trước khi đi sâu vào phần còn lại của câu trả lời của tôi, di chuyển ngữ nghĩa, vv không hoàn toàn thay thế ET như câu trả lời của tôi minh họa dưới đây.

Chi tiết trả lời

ETS thường trở về đối tượng proxy để trì hoãn việc đánh giá cho đến sau này, vì vậy không có lợi ích rõ ràng trước mắt của C++ 11 tính năng ngôn ngữ cho đến khi mã kích hoạt các tính toán. Điều đó nói rằng, một trong những sẽ không muốn viết mã ET, tuy nhiên, mà gây nên thời gian chạy mã thế hệ trong việc xây dựng cây biểu hiện với các proxy. Các ngữ nghĩa di chuyển của C++ 11 và chuyển tiếp hoàn hảo có thể giúp tránh các chi phí như vậy nếu xảy ra. (Như vậy sẽ không thể thực hiện được trong C++ 03.)

Về cơ bản, khi viết ETs, người ta muốn khai thác các tính năng ngôn ngữ theo cách tạo mã tối ưu khi chức năng thành viên của các đối tượng proxy liên quan được viện dẫn. Trong C++ 11, điều này sẽ bao gồm việc sử dụng chuyển tiếp hoàn hảo, di chuyển ngữ nghĩa trên sao chép, vv nếu như vậy thực sự vẫn cần thiết hơn và trên những gì trình biên dịch có thể đã làm.Tên của trò chơi là để giảm thiểu thời gian chạy mã được tạo ra và/hoặc tối đa hóa tốc độ thời gian chạy và/hoặc giảm thiểu chi phí thời gian chạy.

Tôi thực sự muốn thử một số ET với các tính năng C++ 11 để xem liệu tôi có thể tách tất cả các loại cá thể tạm thời trung gian với biểu thức a = b + c + d; hay không. (Vì đây chỉ là một kỳ nghỉ vui vẻ từ hoạt động bình thường của tôi nên tôi không so sánh nó với hoặc viết mã ET hoàn toàn bằng cách sử dụng C++ 03. Ngoài ra tôi cũng không lo lắng về tất cả các khía cạnh của mã đánh bóng xuất hiện bên dưới.)

Để bắt đầu, tôi đã không sử dụng lambdas - tôi thích sử dụng các loại và chức năng rõ ràng-- vì vậy tôi sẽ không tranh luận về/chống lại lambdas đối với câu hỏi của bạn. Tôi đoán là chúng sẽ tương tự như sử dụng functors và không tốt hơn mã non-ET bên dưới (tức là, di chuyển sẽ được yêu cầu) - ít nhất là cho đến khi trình biên dịch có thể tự động tối ưu hóa lambdas bằng cách sử dụng các ET nội bộ của chúng. Mã tôi đã viết, tuy nhiên, khai thác di chuyển ngữ nghĩa và chuyển tiếp hoàn hảo. Đây là những gì tôi đã bắt đầu với kết quả và sau đó cuối cùng trình bày mã.

Tôi đã tạo một lớp học math_vector<N> nơi N==3 và nó xác định một cá thể riêng bên trong là std::array<long double, N>. Các thành viên là một hàm tạo mặc định, sao chép và di chuyển các hàm tạo và các phép gán, một hàm tạo danh sách khởi tạo, một hàm hủy, thành viên hoán đổi(), toán tử [] để truy cập các phần tử của vectơ và toán tử + =. Sử dụng mà không bất kỳ mẫu biểu, mã này:

{ 
    cout << "CASE 1:\n"; 
    math_vector<3> a{1.0, 1.1, 1.2}; 
    math_vector<3> b{2.0, 2.1, 2.2}; 
    math_vector<3> c{3.0, 3.1, 3.2}; 
    math_vector<3> d{4.0, 4.1, 4.2}; 
    math_vector<3> result = a + b + c + d; 
    cout << '[' << &result << "]: " << result << "\n"; 
} 

kết quả đầu ra (khi biên soạn với clang++ 3.1 hoặc g++ 4.8 với - std=c++11 -O3):

CASE 1: 
0x7fff8d6edf50: math_vector(initlist) 
0x7fff8d6edef0: math_vector(initlist) 
0x7fff8d6ede90: math_vector(initlist) 
0x7fff8d6ede30: math_vector(initlist) 
0x7fff8d6edd70: math_vector(copy: 0x7fff8d6edf50) 
0x7fff8d6edda0: math_vector(move: 0x7fff8d6edd70) 
0x7fff8d6eddd0: math_vector(move: 0x7fff8d6edda0) 
0x7fff8d6edda0: ~math_vector() 
0x7fff8d6edd70: ~math_vector() 
[0x7fff8d6eddd0]: (10,10.4,10.8) 
0x7fff8d6eddd0: ~math_vector() 
0x7fff8d6ede30: ~math_vector() 
0x7fff8d6ede90: ~math_vector() 
0x7fff8d6edef0: ~math_vector() 
0x7fff8d6edf50: ~math_vector() 

tức, bốn trường hợp xây dựng rõ ràng sử dụng danh sách khởi tạo (tức là , các mặt hàng initlist), biến số result (ví dụ: 0x7fff8d6eddd0) và cũng tạo thêm ba đối tượng sao chép và di chuyển.

Để chỉ tập trung vào là tạm thời và di chuyển, tôi đã tạo ra một trường hợp thứ hai mà chỉ tạo ra result như một tên biến --all khác là rvalues:

{ 
    cout << "CASE 2:\n"; 
    math_vector<3> result = 
    math_vector<3>{1.0, 1.1, 1.2} + 
    math_vector<3>{2.0, 2.1, 2.2} + 
    math_vector<3>{3.0, 3.1, 3.2} + 
    math_vector<3>{4.0, 4.1, 4.2} 
    ; 
    cout << '[' << &result << "]: " << result << "\n"; 
} 

mà kết quả đầu ra này (một lần nữa khi ETS KHÔNG sử dụng) :

CASE 2: 
0x7fff8d6edcb0: math_vector(initlist) 
0x7fff8d6edc50: math_vector(initlist) 
0x7fff8d6edce0: math_vector(move: 0x7fff8d6edcb0) 
0x7fff8d6edbf0: math_vector(initlist) 
0x7fff8d6edd10: math_vector(move: 0x7fff8d6edce0) 
0x7fff8d6edb90: math_vector(initlist) 
0x7fff8d6edd40: math_vector(move: 0x7fff8d6edd10) 
0x7fff8d6edb90: ~math_vector() 
0x7fff8d6edd10: ~math_vector() 
0x7fff8d6edbf0: ~math_vector() 
0x7fff8d6edce0: ~math_vector() 
0x7fff8d6edc50: ~math_vector() 
0x7fff8d6edcb0: ~math_vector() 
[0x7fff8d6edd40]: (10,10.4,10.8) 
0x7fff8d6edd40: ~math_vector() 

tốt hơn: chỉ tạo thêm các đối tượng di chuyển.

Nhưng tôi muốn tốt hơn: Tôi muốn zero temporaries thêm và có mã như thể tôi cứng mã hóa nó với một caveat mã hóa thông thường: tất cả các loại instantiated rõ ràng vẫn sẽ được tạo ra (ví dụ, bốn initlist nhà thầu và result). Để thực hiện điều này tôi sau đó thêm vào biểu mẫu mã như sau:

  1. một proxy math_vector_expr<LeftExpr,BinaryOp,RightExpr> lớp được tạo ra để giữ một biểu thức không tính nào,
  2. một proxy plus_op lớp được tạo ra để giữ hoạt động Ngoài ra,
  3. một hàm tạo đã được thêm vào math_vector để chấp nhận đối tượng math_vector_expr và,
  4. chức năng thành viên "khởi động" đã được thêm vào để kích hoạt việc tạo mẫu biểu thức.

Kết quả sử dụng ET là tuyệt vời: không có thêm thời gian trong cả hai trường hợp! Hai trường hợp trước đây ở trên đầu ra hiện tại:

CASE 1: 
0x7fffe7180c60: math_vector(initlist) 
0x7fffe7180c90: math_vector(initlist) 
0x7fffe7180cc0: math_vector(initlist) 
0x7fffe7180cf0: math_vector(initlist) 
0x7fffe7180d20: math_vector(expr: 0x7fffe7180d90) 
[0x7fffe7180d20]: (10,10.4,10.8) 
0x7fffe7180d20: ~math_vector() 
0x7fffe7180cf0: ~math_vector() 
0x7fffe7180cc0: ~math_vector() 
0x7fffe7180c90: ~math_vector() 
0x7fffe7180c60: ~math_vector() 

CASE 2: 
0x7fffe7180dd0: math_vector(initlist) 
0x7fffe7180e20: math_vector(initlist) 
0x7fffe7180e70: math_vector(initlist) 
0x7fffe7180eb0: math_vector(initlist) 
0x7fffe7180d20: math_vector(expr: 0x7fffe7180dc0) 
0x7fffe7180eb0: ~math_vector() 
0x7fffe7180e70: ~math_vector() 
0x7fffe7180e20: ~math_vector() 
0x7fffe7180dd0: ~math_vector() 
[0x7fffe7180d20]: (10,10.4,10.8) 
0x7fffe7180d20: ~math_vector() 

tức là 5 cuộc gọi hàm tạo và 5 cuộc gọi hủy trong mỗi trường hợp. Trong thực tế, nếu bạn yêu cầu trình biên dịch để tạo mã lắp ráp giữa 4 initlist cuộc gọi constructor và xuất ra của result ai được chuỗi xinh đẹp này mã lắp ráp:

fldt 128(%rsp) 
leaq 128(%rsp), %rdi 
leaq 80(%rsp), %rbp 
fldt 176(%rsp) 
faddp %st, %st(1) 
fldt 224(%rsp) 
faddp %st, %st(1) 
fldt 272(%rsp) 
faddp %st, %st(1) 
fstpt 80(%rsp) 
fldt 144(%rsp) 
fldt 192(%rsp) 
faddp %st, %st(1) 
fldt 240(%rsp) 
faddp %st, %st(1) 
fldt 288(%rsp) 
faddp %st, %st(1) 
fstpt 96(%rsp) 
fldt 160(%rsp) 
fldt 208(%rsp) 
faddp %st, %st(1) 
fldt 256(%rsp) 
faddp %st, %st(1) 
fldt 304(%rsp) 
faddp %st, %st(1) 
fstpt 112(%rsp) 

với g++clang++ đầu ra tương tự (thậm chí nhỏ hơn) mã. Không có cuộc gọi chức năng nào, v.v. - điều chỉnh một loạt các tính năng bổ sung CHÍNH XÁC là điều bạn muốn!

Mã C++ 11 để đạt được điều này sau. Đơn giản chỉ cần #define DONT_USE_EXPR_TEMPL để không sử dụng ET hoặc không xác định nó ở tất cả để sử dụng ETs.

#include <array> 
#include <algorithm> 
#include <initializer_list> 
#include <type_traits> 
#include <iostream> 

//#define DONT_USE_EXPR_TEMPL 

//=========================================================================== 

template <std::size_t N> class math_vector; 

template < 
    typename LeftExpr, 
    typename BinaryOp, 
    typename RightExpr 
> 
class math_vector_expr 
{ 
    public: 
    math_vector_expr() = delete; 

    math_vector_expr(LeftExpr l, RightExpr r) : 
     l_(std::forward<LeftExpr>(l)), 
     r_(std::forward<RightExpr>(r)) 
    { 
    } 

    // Prohibit copying... 
    math_vector_expr(math_vector_expr const&) = delete; 
    math_vector_expr& operator =(math_vector_expr const&) = delete; 

    // Allow moves... 
    math_vector_expr(math_vector_expr&&) = default; 
    math_vector_expr& operator =(math_vector_expr&&) = default; 

    template <typename RE> 
    auto operator +(RE&& re) const -> 
     math_vector_expr< 
     math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&, 
     BinaryOp, 
     decltype(std::forward<RE>(re)) 
     > 
    { 
     return 
     math_vector_expr< 
      math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&, 
      BinaryOp, 
      decltype(std::forward<RE>(re)) 
     >(*this, std::forward<RE>(re)) 
     ; 
    } 

    auto le() -> 
     typename std::add_lvalue_reference<LeftExpr>::type 
     { return l_; } 

    auto le() const -> 
     typename std::add_lvalue_reference< 
     typename std::add_const<LeftExpr>::type 
     >::type 
     { return l_; } 

    auto re() -> 
     typename std::add_lvalue_reference<RightExpr>::type 
     { return r_; } 

    auto re() const -> 
     typename std::add_lvalue_reference< 
     typename std::add_const<RightExpr>::type 
     >::type 
     { return r_; } 

    auto operator [](std::size_t index) const -> 
     decltype(
     BinaryOp::apply(this->le()[index], this->re()[index]) 
    ) 
    { 
     return BinaryOp::apply(le()[index], re()[index]); 
    } 

    private: 
    LeftExpr l_; 
    RightExpr r_; 
}; 

//=========================================================================== 

template <typename T> 
struct plus_op 
{ 
    static T apply(T const& a, T const& b) 
    { 
    return a + b; 
    } 

    static T apply(T&& a, T const& b) 
    { 
    a += b; 
    return std::move(a); 
    } 

    static T apply(T const& a, T&& b) 
    { 
    b += a; 
    return std::move(b); 
    } 

    static T apply(T&& a, T&& b) 
    { 
    a += b; 
    return std::move(a); 
    } 
}; 

//=========================================================================== 

template <std::size_t N> 
class math_vector 
{ 
    using impl_type = std::array<long double, N>; 

    public: 
    math_vector() 
    { 
     using namespace std; 
     fill(begin(v_), end(v_), impl_type{}); 
     std::cout << this << ": math_vector()" << endl; 
    } 

    math_vector(math_vector const& mv) noexcept 
    { 
     using namespace std; 
     copy(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector(copy: " << &mv << ")" << endl; 
    } 

    math_vector(math_vector&& mv) noexcept 
    { 
     using namespace std; 
     move(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector(move: " << &mv << ")" << endl; 
    } 

    math_vector(std::initializer_list<typename impl_type::value_type> l) 
    { 
     using namespace std; 
     copy(begin(l), end(l), begin(v_)); 
     std::cout << this << ": math_vector(initlist)" << endl; 
    } 

    math_vector& operator =(math_vector const& mv) noexcept 
    { 
     using namespace std; 
     copy(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector op =(copy: " << &mv << ")" << endl; 
     return *this; 
    } 

    math_vector& operator =(math_vector&& mv) noexcept 
    { 
     using namespace std; 
     move(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector op =(move: " << &mv << ")" << endl; 
     return *this; 
    } 

    ~math_vector() 
    { 
     using namespace std; 
     std::cout << this << ": ~math_vector()" << endl; 
    } 

    void swap(math_vector& mv) 
    { 
     using namespace std; 
     for (std::size_t i = 0; i<N; ++i) 
     swap(v_[i], mv[i]); 
    } 

    auto operator [](std::size_t index) const 
     -> typename impl_type::value_type const& 
    { 
     return v_[index]; 
    } 

    auto operator [](std::size_t index) 
     -> typename impl_type::value_type& 
    { 
     return v_[index]; 
    } 

    math_vector& operator +=(math_vector const& b) 
    { 
     for (std::size_t i = 0; i<N; ++i) 
     v_[i] += b[i]; 
     return *this; 
    } 

    #ifndef DONT_USE_EXPR_TEMPL 

    template <typename LE, typename Op, typename RE> 
    math_vector(math_vector_expr<LE,Op,RE>&& mve) 
    { 
     for (std::size_t i = 0; i < N; ++i) 
     v_[i] = mve[i]; 
     std::cout << this << ": math_vector(expr: " << &mve << ")" << std::endl; 
    } 

    template <typename RightExpr> 
    math_vector& operator =(RightExpr&& re) 
    { 
     for (std::size_t i = 0; i<N; ++i) 
     v_[i] = re[i]; 
     return *this; 
    } 

    template <typename RightExpr> 
    math_vector& operator +=(RightExpr&& re) 
    { 
     for (std::size_t i = 0; i<N; ++i) 
     v_[i] += re[i]; 
     return *this; 
    } 

    template <typename RightExpr> 
    auto operator +(RightExpr&& re) const -> 
     math_vector_expr< 
     math_vector const&, 
     plus_op<typename impl_type::value_type>, 
     decltype(std::forward<RightExpr>(re)) 
     > 
    { 
     return 
     math_vector_expr< 
      math_vector const&, 
      plus_op<typename impl_type::value_type>, 
      decltype(std::forward<RightExpr>(re)) 
     >(
      *this, 
      std::forward<RightExpr>(re) 
     ) 
     ; 
    } 

    #endif // #ifndef DONT_USE_EXPR_TEMPL 

    private: 
    impl_type v_; 
}; 

//=========================================================================== 

template <std::size_t N> 
inline void swap(math_vector<N>& a, math_vector<N>& b) 
{ 
    a.swap(b); 
} 

//=========================================================================== 

#ifdef DONT_USE_EXPR_TEMPL 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N> const& a, 
    math_vector<N> const& b 
) 
{ 
    math_vector<N> retval(a); 
    retval += b; 
    return retval; 
} 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N>&& a, 
    math_vector<N> const& b 
) 
{ 
    a += b; 
    return std::move(a); 
} 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N> const& a, 
    math_vector<N>&& b 
) 
{ 
    b += a; 
    return std::move(b); 
} 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N>&& a, 
    math_vector<N>&& b 
) 
{ 
    a += std::move(b); 
    return std::move(a); 
} 

#endif // #ifdef DONT_USE_EXPR_TEMPL 

//=========================================================================== 

template <std::size_t N> 
std::ostream& operator <<(std::ostream& os, math_vector<N> const& mv) 
{ 
    os << '('; 
    for (std::size_t i = 0; i < N; ++i) 
    os << mv[i] << ((i+1 != N) ? ',' : ')'); 
    return os; 
} 

//=========================================================================== 

int main() 
{ 
    using namespace std; 

    try 
    { 
    { 
     cout << "CASE 1:\n"; 
     math_vector<3> a{1.0, 1.1, 1.2}; 
     math_vector<3> b{2.0, 2.1, 2.2}; 
     math_vector<3> c{3.0, 3.1, 3.2}; 
     math_vector<3> d{4.0, 4.1, 4.2}; 
     math_vector<3> result = a + b + c + d; 
     cout << '[' << &result << "]: " << result << "\n"; 
    } 
    cout << endl; 
    { 
     cout << "CASE 2:\n"; 
     math_vector<3> result = 
     math_vector<3>{1.0, 1.1, 1.2} + 
     math_vector<3>{2.0, 2.1, 2.2} + 
     math_vector<3>{3.0, 3.1, 3.2} + 
     math_vector<3>{4.0, 4.1, 4.2} 
     ; 
     cout << '[' << &result << "]: " << result << "\n"; 
    } 
    } 
    catch (...) 
    { 
    return 1; 
    } 
} 

//=========================================================================== 
+2

Cảm ơn Paul vì câu trả lời của bạn. Trong mã của bạn, bạn đã áp dụng kỹ thuật mẫu biểu thức được pha trộn với các kỹ thuật C++ 11 mới. Làm tốt lắm. Tôi thích các lớp hoạt động quá tải. Và có, tôi đồng ý rằng lambdas về cơ bản sẽ làm cho hiệu suất tương tự như có cấu trúc được đặt tên. Tôi đã cố gắng phá vỡ kỹ thuật ET, nếu bạn ưa thích câu hỏi được mở rộng bởi mã của tôi. – ritter

+1

Bạn được chào đón! Câu hỏi của bạn cũng rất phù hợp.Các bổ sung mới (đặc biệt là các mẫu biến thể, di chuyển ngữ nghĩa, và chuyển tiếp hoàn hảo) thực sự khiến chúng ta phải thực hiện các bước để điều chỉnh và làm mọi thứ một cách đơn giản nữa - giống như khi chúng ta bắt đầu viết mã! Có lẽ quá lâu, chúng tôi đã tối ưu hóa để bỏ qua trình biên dịch một cách hiệu quả. Với C++ 11 điều này không còn cần thiết cũng như suy nghĩ ra/mã đơn giản mang lại hiệu suất tốt hơn/tối ưu chỉ bằng cách cho phép trình biên dịch làm những gì nó làm. Vì vậy, với C++ 11, tôi cố gắng nhắc nhở bản thân mình để chống lại sự cám dỗ để tối ưu hóa nhưng để suy nghĩ lại đơn giản hơn. –

+0

@Frank: Tôi nghĩ câu trả lời này đã sẵn sàng để được chấp nhận, phải không? – marton78

6

Đây là phiên bản được sửa của mã Paul Preney. Tôi đã thông báo cho tác giả qua email và nhận xét; Tôi đã viết bản chỉnh sửa nhưng đã bị người đánh giá không đủ điều kiện từ chối.

Lỗi trong mã ban đầu là tham số mẫu BinaryOp của math_vector_expr :: toán tử + được sửa.

#include <array> 
#include <algorithm> 
#include <initializer_list> 
#include <type_traits> 
#include <iostream> 

//#define DONT_USE_EXPR_TEMPL 

//=========================================================================== 

template <std::size_t N> class math_vector; 
template <typename T> struct plus_op; 


template < 
    typename LeftExpr, 
    typename BinaryOp, 
    typename RightExpr 
> 
class math_vector_expr 
{ 
    public: 
    typedef typename std::remove_reference<LeftExpr>::type::value_type value_type; 

    math_vector_expr() = delete; 

    math_vector_expr(LeftExpr l, RightExpr r) : 
     l_(std::forward<LeftExpr>(l)), 
     r_(std::forward<RightExpr>(r)) 
    { 
    } 

    // Prohibit copying... 
    math_vector_expr(math_vector_expr const&) = delete; 
    math_vector_expr& operator =(math_vector_expr const&) = delete; 

    // Allow moves... 
    math_vector_expr(math_vector_expr&&) = default; 
    math_vector_expr& operator =(math_vector_expr&&) = default; 

    template <typename RE> 
    auto operator +(RE&& re) const -> 
     math_vector_expr< 
     math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&, 
     plus_op<value_type>, 
     decltype(std::forward<RE>(re)) 
     > 
    { 
     return 
     math_vector_expr< 
      math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&, 
      plus_op<value_type>, 
      decltype(std::forward<RE>(re)) 
     >(*this, std::forward<RE>(re)) 
     ; 
    } 

    auto le() -> 
     typename std::add_lvalue_reference<LeftExpr>::type 
     { return l_; } 

    auto le() const -> 
     typename std::add_lvalue_reference< 
     typename std::add_const<LeftExpr>::type 
     >::type 
     { return l_; } 

    auto re() -> 
     typename std::add_lvalue_reference<RightExpr>::type 
     { return r_; } 

    auto re() const -> 
     typename std::add_lvalue_reference< 
     typename std::add_const<RightExpr>::type 
     >::type 
     { return r_; } 

    auto operator [](std::size_t index) const -> 
     value_type 
    { 
     return BinaryOp::apply(le()[index], re()[index]); 
    } 

    private: 
    LeftExpr l_; 
    RightExpr r_; 
}; 

//=========================================================================== 

template <typename T> 
struct plus_op 
{ 
    static T apply(T const& a, T const& b) 
    { 
    return a + b; 
    } 

    static T apply(T&& a, T const& b) 
    { 
    a += b; 
    return std::move(a); 
    } 

    static T apply(T const& a, T&& b) 
    { 
    b += a; 
    return std::move(b); 
    } 

    static T apply(T&& a, T&& b) 
    { 
    a += b; 
    return std::move(a); 
    } 
}; 

//=========================================================================== 

template <std::size_t N> 
class math_vector 
{ 
    using impl_type = std::array<long double, N>; 

    public: 
    typedef typename impl_type::value_type value_type; 

    math_vector() 
    { 
     using namespace std; 
     fill(begin(v_), end(v_), impl_type{}); 
     std::cout << this << ": math_vector()" << endl; 
    } 

    math_vector(math_vector const& mv) noexcept 
    { 
     using namespace std; 
     copy(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector(copy: " << &mv << ")" << endl; 
    } 

    math_vector(math_vector&& mv) noexcept 
    { 
     using namespace std; 
     move(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector(move: " << &mv << ")" << endl; 
    } 

    math_vector(std::initializer_list<value_type> l) 
    { 
     using namespace std; 
     copy(begin(l), end(l), begin(v_)); 
     std::cout << this << ": math_vector(initlist)" << endl; 
    } 

    math_vector& operator =(math_vector const& mv) noexcept 
    { 
     using namespace std; 
     copy(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector op =(copy: " << &mv << ")" << endl; 
     return *this; 
    } 

    math_vector& operator =(math_vector&& mv) noexcept 
    { 
     using namespace std; 
     move(begin(mv.v_), end(mv.v_), begin(v_)); 
     std::cout << this << ": math_vector op =(move: " << &mv << ")" << endl; 
     return *this; 
    } 

    ~math_vector() 
    { 
     using namespace std; 
     std::cout << this << ": ~math_vector()" << endl; 
    } 

    void swap(math_vector& mv) 
    { 
     using namespace std; 
     for (std::size_t i = 0; i<N; ++i) 
     swap(v_[i], mv[i]); 
    } 

    auto operator [](std::size_t index) const 
     -> value_type const& 
    { 
     return v_[index]; 
    } 

    auto operator [](std::size_t index) 
     -> value_type& 
    { 
     return v_[index]; 
    } 

    math_vector& operator +=(math_vector const& b) 
    { 
     for (std::size_t i = 0; i<N; ++i) 
     v_[i] += b[i]; 
     return *this; 
    } 

    #ifndef DONT_USE_EXPR_TEMPL 

    template <typename LE, typename Op, typename RE> 
    math_vector(math_vector_expr<LE,Op,RE>&& mve) 
    { 
     for (std::size_t i = 0; i < N; ++i) 
     v_[i] = mve[i]; 
     std::cout << this << ": math_vector(expr: " << &mve << ")" << std::endl; 
    } 

    template <typename RightExpr> 
    math_vector& operator =(RightExpr&& re) 
    { 
     for (std::size_t i = 0; i<N; ++i) 
     v_[i] = re[i]; 
     return *this; 
    } 

    template <typename RightExpr> 
    math_vector& operator +=(RightExpr&& re) 
    { 
     for (std::size_t i = 0; i<N; ++i) 
     v_[i] += re[i]; 
     return *this; 
    } 

    template <typename RightExpr> 
    auto operator +(RightExpr&& re) const -> 
     math_vector_expr< 
     math_vector const&, 
     plus_op<value_type>, 
     decltype(std::forward<RightExpr>(re)) 
     > 
    { 
     return 
     math_vector_expr< 
      math_vector const&, 
      plus_op<value_type>, 
      decltype(std::forward<RightExpr>(re)) 
     >(
      *this, 
      std::forward<RightExpr>(re) 
     ) 
     ; 
    } 

    #endif // #ifndef DONT_USE_EXPR_TEMPL 

    private: 
    impl_type v_; 
}; 

//=========================================================================== 

template <std::size_t N> 
inline void swap(math_vector<N>& a, math_vector<N>& b) 
{ 
    a.swap(b); 
} 

//=========================================================================== 

#ifdef DONT_USE_EXPR_TEMPL 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N> const& a, 
    math_vector<N> const& b 
) 
{ 
    math_vector<N> retval(a); 
    retval += b; 
    return retval; 
} 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N>&& a, 
    math_vector<N> const& b 
) 
{ 
    a += b; 
    return std::move(a); 
} 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N> const& a, 
    math_vector<N>&& b 
) 
{ 
    b += a; 
    return std::move(b); 
} 

template <std::size_t N> 
inline math_vector<N> operator +(
    math_vector<N>&& a, 
    math_vector<N>&& b 
) 
{ 
    a += std::move(b); 
    return std::move(a); 
} 

#endif // #ifdef DONT_USE_EXPR_TEMPL 

//=========================================================================== 

template <std::size_t N> 
std::ostream& operator <<(std::ostream& os, math_vector<N> const& mv) 
{ 
    os << '('; 
    for (std::size_t i = 0; i < N; ++i) 
    os << mv[i] << ((i+1 != N) ? ',' : ')'); 
    return os; 
} 

//=========================================================================== 

int main() 
{ 
    using namespace std; 

    try 
    { 
    { 
     cout << "CASE 1:\n"; 
     math_vector<3> a{1.0, 1.1, 1.2}; 
     math_vector<3> b{2.0, 2.1, 2.2}; 
     math_vector<3> c{3.0, 3.1, 3.2}; 
     math_vector<3> d{4.0, 4.1, 4.2}; 
     math_vector<3> result = a + b + c + d; 
     cout << '[' << &result << "]: " << result << "\n"; 
    } 
    cout << endl; 
    { 
     cout << "CASE 2:\n"; 
     math_vector<3> result = 
     math_vector<3>{1.0, 1.1, 1.2} + 
     math_vector<3>{2.0, 2.1, 2.2} + 
     math_vector<3>{3.0, 3.1, 3.2} + 
     math_vector<3>{4.0, 4.1, 4.2} 
     ; 
     cout << '[' << &result << "]: " << result << "\n"; 
    } 
    } 
    catch (...) 
    { 
    return 1; 
    } 
} 

//===========================================================================