2012-08-04 68 views
35

让我们看的表达模板之一特别的益处:ET的可用于避免在存储器矢量大小的临时对象中出现的过载操作符,如:表达模板和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; 
} 

在C++中11这个函数的返回语句应用移动语义。矢量的副本。这是一场胜利。

但是,如果我看一个简单的语句,比如

d = a + b + c; 

我看到上面的函数被调用两次(两个operator+),而最终的分配可以通过移动语义来完成。

共执行2个循环。意思是我暂时放了一张,然后马上阅读。对于大型媒体而言,这会脱离缓存。这比表达式模板更糟糕。他们可以在一个循环中完成整个事情。专家组可以执行上述的等效代码:

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

我想用移动语义或任何其他新的功能lambda表达式一起是否能够像外星人一样好。有什么想法吗?

编辑:

基本上,使用ET技术编译器生成解析树 类似于代数表达式与它的 类型系统。该树由内部节点和叶节点组成。内部节点表示操作(加法,乘法等)和叶节点表示对数据对象的引用。

我试图用一个 堆栈机器的方式来思考整个计算过程:从操作堆栈中取一个操作并从参数堆栈中取出下一个参数并评估操作。 将结果放回堆栈等待操作。

为了表示这两个不同的对象(操作堆栈和数据 叶栈)我捆绑在一起的操作的std::tuplestd::tuple用于数据离开成std::pair<>。最初,我 使用std:vector,但导致运行时间开销。

整个过程分两个阶段:初始化操作和参数堆栈的堆栈机初始化 。并且通过将配对的容器 分配给矢量来触发评估阶段。

我创建的类Vec保持的私人array<int,5>(所述 有效载荷),并且设有一个重载的赋值运算符 取“表达”。

全球operator*超载为采取 Vec和“表达”,使正确处理同样的情况下 我们已经不仅仅是a*b更多的所有组合。(请注意,我换这个 教育为例,乘法 - 基本上可以快速识别 的imull在汇编。)

什么是评估前先进行启动的“提取”的 值了参与Vec的对象并初始化参数 堆栈。这是必要的,以避免不同类型的对象围绕着:可转位向量和不可索引的结果。这就是 Extractor的用途。再好的事情:在这种情况下使用的变量模板导致没有运行时开销(所有这些都在编译时间 完成)。

整件事情奏效。该表达式很好地评估(我也 添加了,但这是遗漏在这里,以适应代码)。在 以下,您可以看到汇编器输出。只是原始计算,就像你想要的那样:与ET技术相提并论。

Upshot。 C++ 11的新语言特性提供了可变参数 模板(与模板元编程一起)打开编译时计算的 区域。我在这里展示了如何使用 可变参数模板的优点来产生与传统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; 
} 

g++-4.6 -O3生成汇编(我不得不把一些运行时的依赖到载体中初始化,这样编译器不计算整个事情在编译的时候,你实际看到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

您应该查看[copy elision and RVO](http://en.wikipedia.org/wiki/Copy_elision)。此外,通过值传递其中一个向量而不是制作自己的'tmp'副本可能会有所帮助。 – juanchopanza 2012-08-04 13:39:15

+0

按价值传递无法帮助(仍然复制)。 (N)RVO无助于消除额外的循环 – ritter 2012-08-04 13:43:41

+0

@Frank:你确定吗?这是RVO的强有力候选人。 – akappa 2012-08-04 13:47:43

回答

45

我想知道lambda表达式是否与移动语义或其他新特性一起可以做得如同ET一样好。有什么想法吗?

快速解答

移动语义不靠自己--techniques如表达式模板(ETS)这样仍然需要在C++ 11共万能消除开销如周围移动数据!因此,在深入解答我的答案之前,快速回答您的问题,移动语义等不会完全取代ET,因为我的答案如下所示。

详细的解答

专家组通常返回代理对象推迟评估,直到后来,所以的C++ 11个语言特性没有立即明显的好处,直到触发计算的代码。也就是说,人们不想编写ET代码,但是,这会在代理构建表达式树期间触发运行时代码生成。很好,C++ 11的移动语义和完美的转发可以帮助避免这种情况发生。 (这在C++ 03中是不可能的。)

基本上,在编写ET时,一旦想要利用语言特性来产生最优代码,一旦涉及的代理对象的成员函数调用。在C++ 11中,这将包括使用完美的转发,通过复制移动语义等,如果实际上仍然需要超过编译器已经可以完成的工作。游戏的名称是为了最小化生成的运行时代码和/或最大化运行时速度和/或最小化运行时间开销。

我想实际尝试一些使用C++ 11功能的ET,以查看是否可以用a = b + c + d;表达式消除所有中间临时实例类型。 (因为这只是我正常活动的一个有趣的休息,所以我没有将它与纯粹使用C++ 03的ET代码进行比较,也没有担心下面出现的代码抛光的所有方面)。首先,我没有使用lambda表达式 - 因为我更喜欢使用显式类型和函数 - 所以我不会为你的问题争论/反对lambda表达式。我的猜测是,它们会类似于使用函子,并且性能不如下面的非ET代码更好(即,需要移动) - 至少编译器可以使用它们自己的内部ET来自动优化lambda。然而,我写的代码利用了移动语义和完美的转发。这是我从结果开始并最终呈现代码的过程。

我创建了一个math_vector<N>类,其中N==3它定义了一个内部私有实例std::array<long double, N>。成员是默认构造函数,复制和移动构造函数和赋值,初始化程序列表构造函数,析构函数,swap()成员,operator []以访问vector和operator + =的元素。使用而无需任何表达式模板,此代码:

{ 
    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"; 
} 

输出(当与clang++ 3.1或g++ 4.8编译 - 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() 

即,使用初始化列表四个明确的构造实例(即,initlist项),result变量(即0x7fff8d6eddd0),并且还使附加的三个对象复制和移动。

为了只着眼于临时对象和移动,我创建了仅创建result作为命名变量--all其它的是右值的第二情况下:

{ 
    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"; 
} 

此(再次当不使用ETS)输出:

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() 

哪个更好:只创建额外的移动对象。

但我就想好了:我想零个额外的临时变量,并有代码,如果我用一个正常的编码警告硬编码的:所有的显式实例类型仍然会创建(即四个initlist建设者和result )。要做到这一点,我再补充表情模板代码如下:

  1. 代理math_vector_expr<LeftExpr,BinaryOp,RightExpr>类创建持有尚未计算表达式,
  2. 代理plus_op类是为了保持加法运算,
  3. 一个构造函数被添加到math_vector以接受math_vector_expr对象,并且,
  4. “starter”成员函数被添加以触发表达式模板的创建。

使用ET的结果非常棒:在任何情况下都不需要额外的临时对象!前两例,现将以上输出:

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() 

即,精确至5构造函数调用和在每种情况下5所析构函数调用。事实上,如果你问的编译器来生成4 initlist构造函数调用和result一个的输出之间的汇编代码获取这个美丽的汇编代码字符串:

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) 

g++clang++相似(甚至更小)输出码。没有函数调用等 - 只是一堆补充,这正是人们想要的!

C++ 11的代码如下。简单地#define DONT_USE_EXPR_TEMPL不使用ET或根本不定义它使用ET。

#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++ 11技术淹没的表达式模板技术 。做得好。我喜欢重载的操作类。是的,我同意lambdas基本上会呈现与命名结构相同的性能。我试图绕过ET技术,如果你喜欢这个问题被我的代码扩展。 – ritter 2012-08-05 17:41:46

+1

不客气!你的问题也非常相关。新增加的内容(特别是可变参数模板,移动语义和完美转发)实际上让我们所有人都采取措施重新调整,并以更简单的方式再次完成任务 - 就像我们开始编写代码一样!也许很久以来,我们都在进行优化以有效地绕过编译器。使用C++ 11时,不再需要这样的代码,只需让编译器完成它所做的工作,即可轻松实现更简单的代码,从而获得更好/最佳的性能。所以在C++ 11中,我试图提醒自己抵制优化的诱惑,但要更简单地重新思考。 – 2012-08-05 18:44:32

+0

@Frank:我认为这个答案已经准备好被接受,不是吗? – marton78 2013-02-12 14:14:46

6

这是Paul Preney的代码的修正版本。我通过电子邮件和评论通知了作者;我编写了一个编辑,但它已被不合格的审阅者拒绝。

原始代码中的错误是math_vector_expr :: operator +的BinaryOp模板参数是固定的。

#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; 
    } 
} 

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