2011-02-25 39 views
13

Giả sử chúng ta muốn áp dụng một loạt các phép biến đổi, int f1(int), int f2(int), int f3(int), vào danh sách các đối tượng. Một cách ngây thơ sẽ làC++ Thiết kế đường ống Iterator

SourceContainer source; 

TempContainer1 temp1; 
transform(source.begin(), source.end(), back_inserter(temp1), f1); 
TempContainer2 temp2; 
transform(temp1.begin(), temp1.end(), back_inserter(temp2), f2); 

TargetContainer target; 
transform(temp2.begin(), temp2.end(), back_inserter(target), f3); 

Giải pháp này đầu tiên không phải là tối ưu vì yêu cầu không gian thêm với temp1temp2. Vì vậy, chúng ta hãy nên thông minh hơn với điều này:

int f123(int n) { return f3(f2(f1(n))); } 
... 
SourceContainer source; 
TargetContainer target; 
transform(source.begin(), source.end(), back_inserter(target), f123); 

Giải pháp này thứ hai là tốt hơn nhiều vì không chỉ mã đơn giản nhưng quan trọng hơn có ít yêu cầu không gian mà không có sự tính toán trung gian.

Tuy nhiên, bố cục f123 phải được xác định tại thời gian biên dịch và do đó được cố định tại thời gian chạy.

Làm cách nào để cố gắng thực hiện điều này một cách hiệu quả nếu thành phần được xác định trong thời gian chạy? Ví dụ: nếu mã này nằm trong dịch vụ RPC và bố cục thực tế - có thể là bất kỳ hoán vị nào của bất kỳ tập con nào của f1, f2f3 - dựa trên các đối số từ cuộc gọi RPC.

+3

+1 Tôi muốn xem những gì mọi người nghĩ ra. – templatetypedef

+1

Bạn biết rằng bạn có thể 'biến đổi' tại chỗ, đúng không? –

+0

Trong ví dụ này, tôi đã sử dụng 'SourceContainer' và' TargetContainer', nhưng các trình vòng lặp có thể đến từ luồng đầu vào và luồng đầu ra, mà không thể được biến đổi tại chỗ. – kirakun

Trả lời

3

EDIT: Phiên bản Làm việc tại http://ideone.com/5GxnW. Phiên bản bên dưới có ý tưởng nhưng không biên dịch. Nó hỗ trợ kiểm tra kiểu thời gian chạy và chạy thành phần hàm thời gian.

Ý tưởng là định nghĩa một lớp hàm chung (unary), và một cách để tạo chúng với kiểm tra kiểu thời gian chạy. Điều này được thực hiện với sự kết hợp của boost::any, boost::function và thành ngữ xóa kiểu.

#include <boost/any.hpp> 
#include <boost/function.hpp> 
#include <boost/shared_ptr.hpp> 


template <typename T> 
struct identity 
{ 
    T operator()(const T& x) { return x; } 
}; 

struct any_function 
{ 
    template <typename Res, typename Arg> 
    any_function(boost::function<Res, Arg> f) 
    { 
     impl = make_impl(f); 
    } 

    boost::any operator()(const boost::any& x) 
    { 
     return impl->invoke(x); 
    } 

    static any_function compose(const any_function& f, 
           const any_function& g) 
    { 
     any_function ans; 
     ans.impl = compose_impl(f.impl, g.impl); 
     return ans; 
    } 

    template <typename T> 
    static any_function id() 
    { 
     using boost::function 
     return any_function(function<T(T)>(identity<T>())); 
    } 

    template <typename Res, typename Arg> 
    boost::function<Res(Arg)> to_function() 
    { 
     using boost::function; 
     return function<Res(Arg)>(to_function_helper(impl)); 
    } 

private: 
    any_function() {} 

    struct impl_type 
    { 
     virtual ~impl_type() {} 
     virtual boost::any invoke(const boost::any&) = 0; 
    }; 

    boost::shared_ptr<impl_type> impl; 

    template <typename Res, typename Arg> 
    static impl_type* make_impl(boost::function<Res(Arg)> f) 
    { 
     using boost::function; 
     using boost::any; 
     using boost::any_cast; 

     class impl : public impl_type 
     { 
      function<Res(Arg)> f; 

      any invoke(const any& x) 
      { 
       const Arg& a = any_cast<Arg>(x); 
       return any(f(a)); 
      } 

     public: 
      impl(function<Res(Arg)> f) : f(f) {} 
     }; 

     return new impl(f); 
    } 

    impl_type* compose_impl(boost::shared_ptr<impl_type> f, 
          boost::shared_ptr<impl_type> g) 
    { 
     using boost::any; 
     using boost::shared_ptr; 

     class impl : public impl_type 
     { 
      shared_ptr<impl> f, g; 

      any invoke(const any& x) 
      { 
       return g->invoke(f->invoke(x)); 
      } 

     public: 
      impl(const shared_ptr<impl>& f, 
       const shared_ptr<impl>& g) 
       : f(f), g(g) 
      {} 
     }; 

     return new impl(f, g); 
    } 

    struct to_function_helper 
    { 
     template <typename Res, typename Arg> 
     Res operator()(const Arg& x) 
     { 
      using boost::any; 
      using boost::any_cast; 

      return any_cast<Res>(p->invoke(any(x))); 
     } 

     to_function_helper(const boost::shared_ptr<impl>& p) : p(p) {} 

    private: 
     boost::shared_ptr<impl> p; 
    }; 
}; 

Bây giờ, chúng ta hãy sử dụng các thuật toán tiêu chuẩn và thực hiện điều này (điều này thậm chí còn hoạt động trên các chuỗi rỗng):

// First function passed is evaluated first. Feel free to change. 
template <typename Arg, typename Res, typename I> 
boost::function<Res(Arg)> pipeline(I begin, I end) 
{ 
    return std::accumulate(begin, end, 
     any_function::id<Arg>, 
     std::ptr_fun(any_function::compose) 
    ).to_function<Res, Arg>(); 
} 

và sử dụng sau đây để áp dụng nó

std::vector<any_function> f; 
std::vector<double> v; 
std::vector<int> result; 

std::transform(v.begin(), v.end(), 
    result.begin(), 
    pipeline<double, int>(f.begin(), f.end()) 
); 

Bạn thậm chí có thể sử dụng boost::transform_iterator

typedef boost::transform_iterator< 
    boost::function<double, int>, 
    std::vector<double>::const_iterator 
> iterator; 

boost::function<double, int> f = pipeline<double, int>(f.begin(), f.end()); 
std::copy(iterator(v.begin(), f), iterator(v.end(), f), result.begin()); 
+0

Mã C++ đẹp nhấn mạnh tinh thần của STL. Đã học một vài điều, bao gồm các lớp địa phương và sử dụng boost :: bất kỳ để mô phỏng các kiểu động trong C++. – kirakun

+0

Bí quyết tuyệt vời với 'danh tính'. Chúng ta có monoid biến đổi nếu chúng ta muốn nó. Bạn đã tự mình tạo ra nó chưa? –

+0

@Oleg: Thuật toán 'accumulate' hơi giống' fold' trong các ngôn ngữ chức năng, và nó cần giá trị khởi đầu. Chúng tôi đang thực sự làm việc trên mức chức năng ở đây, một cái gì đó C + + là đặc biệt tiết tại. –

1

bạn nên sử dụng một functor thay vì chức năng và thông qua cần chuyển đổi chức năng vào constructor functor của

cái gì đó như

typedef int (*FunctionType)(int); 

class Functor 
{ 
    FunctionType m_f1; 
    FunctionType m_f2; 
    FunctionType m_f3; 
public: 
    Functor(FunctionType f1, FunctionType f2, FunctionType f3): 
     m_f1(f1), m_f2(f2), m_f3(f3) 
    {} 
    int operator()(int n) 
    { 
     return (*m_f1)((*m_f2)((*m_f3)(n))); 
    } 
}; 

// ... 

transform(source.begin(), source.end(), back_inserter(temp1), Functor(f1,f2,f3)); 

nếu bạn cần số biến các chức năng sau đó thay đổi Functor constructor chữ ký để sử dụng vector của các chức năng và điền vào vectơ đó trước khi gọi biến đổi.

+0

Có thể tốt hơn với một vectơ các con trỏ hàm. –

+0

@Zan có, vectơ của con trỏ hàm sẽ tốt hơn – rmflow

3
template<class T> 
class compose { 
    typedef T (*f)(T); 

    f first_func; 
    f second_func; 

public: 

    compose(f one,f two) : 
     first_func(one), 
     second_func(two)   
    {} 

    T operator()(T const &input) { 
     T temp = first_func(input); 
     return second_func(temp); 
    } 
}; 

#ifdef TEST 

int f(int x) { return 8 + x; } 
int g(int x) { return 2 * x; } 
int h(int x) { return x * x; } 

#include <iostream> 

int main(int argc, char **argv) { 
    compose<int> x(f, g); 
    compose<int> y(g, f); 

    std::cout << x(6) << std::endl; 
    std::cout << y(6) << std::endl; 

    typedef int (*func)(int); 

    func funcs[] = {f, g, h}; 

    compose<int> z(funcs[atoi(argv[1])], funcs[atoi(argv[2])]); 
    std::cout << z(6); 

    return 0; 
} 

#endif 

Với C++ 0x, chúng tôi có thể sử dụng auto để loại bỏ việc phải chỉ định loại đối số/trả về. Hiện tại, tôi cho rằng chúng giống nhau, mặc dù theo lý thuyết, bạn có thể thích khả năng bao gồm các chuyển đổi trong hỗn hợp.

+0

Có phiên bản Variadic nào hoạt động với functors không? – kirakun

+0

Tôi nghĩ rằng OP đang tìm kiếm thành phần thời gian chạy, làm tăng thêm mức độ phức tạp cho giải pháp. – fbrereto

+0

@kirakun: Tôi chưa bao giờ viết một cuốn sách, nhưng tôi không thấy lý do nào không thể viết được. Mã này đã đủ lớn vào thời điểm đó, bằng cách sử dụng một mẫu ở tất cả đã đẩy giới hạn của các khả năng trình biên dịch. –

0
typedef int (*f_t)(int); 

int f1(int a) { return a + 1; } 
int f2(int a) { return a * 2; } 
int f3(int a) { return a * a; } 

int main() 
{ 
    std::vector<f_t> ff = {f1, f2, f3}; 
    std::vector<int> source = {1, 2, 3, 4}, target; 

    std::transform(source.begin(), source.end(), std::back_inserter(target) 
    , [&](int a) { for (f_t &f : ff) a = f(a); return a; }); 

    // print target 
    std::copy(target.begin(), target.end(), std::ostream_iterator<int,char>(std::cout,"\n")); 
    system("pause"); 
    return 0; 
} 
+0

Rất tốt nếu C++ 0x có thể được triển khai trong sản xuất ngay hôm nay. – kirakun

0

Chỉ xác định một iterator mà những gì bạn muốn:

template<typename T> 
struct source 
{ 
    virtual source<T>& operator++(void) = 0; 
    virtual T operator*(void) = 0; 
    virtual bool atend() = 0; 
}; 

struct source_exhausted 
{ 
}; 

template<typename T> 
bool operator==(const source<T>& comparand, const source_exhausted&) 
{ return comparand.atend(); } 

template<typename T> 
bool operator!=(const source<T>& comparand, const source_exhausted&) 
{ return !comparand.atend(); } 

template<typename T> 
bool operator==(const source_exhausted&, const source<T>& comparand) 
{ return comparand.atend(); } 

template<typename T> 
bool operator!=(const source_exhausted&, const source<T>& comparand) 
{ return !comparand.atend(); } 

template<typename T, typename iterT, typename endT> 
struct source_iterator : source<T> 
{ 
    iterT m_iter; 
    endT m_end; 
    source_iterator(iterT iter, endT end) : m_iter(iter), m_end(end) {} 

    virtual source<T>& operator++(void) { ++m_iter; return *this; } 
    virtual T operator*(void) { return *m_iter; } 
    virtual bool atend() { return m_iter == m_end; } 
}; 
template<typename T, typename iterT, typename endT> 
auto make_source_iterator(iterT iter, endT end) -> source_iterator<decltype(*iter), iterT, endT> 
{ 
    return source_iterator<decltype(*iter), iterT, endT>(iter, end); 
} 
template<typename TContainer> 
auto make_source_iterator(TContainer& c) -> source_iterator<typename TContainer::value_type, decltype(c.begin()), decltype(c.end())> 
{ 
    return source_iterator<typename TContainer::value_type, decltype(c.begin()), decltype(c.end())>(c.begin(), c.end()); 
} 

template<typename TIn, typename TOut, typename TXform> 
struct source_transformer : source<TOut> 
{ 
    source<TIn>& m_src; 
    TXform const m_f; 
    source_transformer(source<TIn>& src, TXform f) : m_f(f), m_src(src) {} 

    virtual source<TOut>& operator++(void) { ++m_src; return *this; } 
    virtual TOut operator*(void) { return m_f(*m_src); } 
    virtual bool atend() { return m_src.atend(); } 
}; 
template<typename TIn, typename TOut, typename TXform> 
auto make_source_transformer(source<TIn>& src, TXform f) -> source_transformer<TIn, decltype(f(*(TIn*)0)), TXform> 
{ 
    return source_transformer<TIn, decltype(f(*(TIn*)0)), TXform>(src, f); 
} 
+0

Tôi không chắc '' auto' và '->' làm gì trong mã của bạn. – kirakun