【问题标题】:How to generate C++ function automatically?如何自动生成 C++ 函数?
【发布时间】:2016-09-07 18:44:20
【问题描述】:

我想写一个c++矩阵运算代码,某些函数的某些操作是一样的。我想知道是否有一些方法可以合并它们?例如:

void add1(vector<vector<double> > &m){
    for(int i = 0; i < m.size(); ++i){
        for(int j = 0; j < m[i].size(); ++j){
            m[i][j] += 1.0;
        }
    }
}

void minus1(vector<vector<double> > &m){
    for(int i = 0; i < m.size(); ++i){
        for(int j = 0; j < m[i].size(); ++j){
            m[i][j] -= 1.0;
        }
    }
}

这两段代码几乎是一样的。如何避免重写两次?有没有模板之类的东西可以自动生成代码?

【问题讨论】:

  • 您可以使用一维数组来存储矩阵的元素(减少到 1 个循环而不是 2 个嵌套循环),然后使用 lambda。
  • 您意识到您正在通过值传递m,对吗?这意味着您的函数将复制传入的向量,并且不会在返回时对其进行更新。
  • @selbie 很抱歉没有意识到这一点。但我的问题仍然存在。
  • 您正在尝试使用 C++ 语法和 C++ 容器编写 C 代码

标签: c++ templates generics code-generation


【解决方案1】:

您可以定义一个函数模板并将迭代元素的核心逻辑放在该函数中。

模板参数可以是函子,可用于定义每个特定调用会发生什么。

// Change m to be a reference so the modifications to the elements
// are visible in the calling function.

template <typename Functor>
void for_each_member(vector<vector<double> >& m, Functor functor)
{
   for(std::size_t i = 0; i < m.size(); ++i){
      for(std::size_t j = 0; j < m[i].size(); ++j){
         functor(m[i][j]);
      }
   }
}

void add1(vector<vector<double> >& m){
   for_each_member(m, [](double& item) { item += 1.0;});
}

void minus1(vector<vector<double> >& m){
   for_each_member(m, [](double& item) { item -= 1.0;});
}

【讨论】:

  • 我已经实现了一个带有仿函数的版本,但是运行速度很慢。速度几乎是直接版的1/5。
  • @maple 这是一个单独的问题。它不应该。我猜你做错了什么,无论是在代码中还是在测量中,或者两者兼而有之。
  • @Drop 我想每次迭代都会调用一个函数,所以速度可能会慢?这是真的吗?
  • @maple - 在代码重用、C++ 的优雅和性能之间肯定存在权衡。如果您想要一个性能优化的解决方案,请停止使用 vector 并回退到直接在指针和/或宏上操作的传统 C 编程机制。 Stack Overflow 上的 C++ 阵营更倾向于给你优雅的 C++ 答案,而不是最优化的代码。
  • 我可以建议使用基于范围的 for 循环吗?
【解决方案2】:

您可以通过以下方式进行概括:

template<typename T, typename F>
void for_each_element(std::vector<std::vector<T>> &m, F &&f) {
  std::for_each(m.begin(), m.end(), [f] (auto &&v) {
    std::transform(v.begin(), v.end(), v.begin(), f);
  });
}

Live Demo

但是,请注意,将矩阵表示为 std::vector&lt;std::vector&lt;T&gt;&gt; 对缓存不是很友好,因为您将矩阵的行分散到整个内存中。如果您有一个自定义矩阵类,它会在内部使用一个 std::vector 并重载 row/column major 访问的下标运算符,那就更好了。

【讨论】:

  • 如果不使用std::forward,为什么还要使用转发引用?
  • @MikeMB 用于代理对象函子,但在显示的代码中您会在哪里使用 std::forward
  • 我既不会使用std::forward,也不会转发引用,而只是按值传递。我的意思是,因为您没有从f 移动或转发-我看不到,与例如F &amp;&amp;f 相比,您获得了什么。 const F&amp; f.
  • @MikeMB: f 可以有可变状态。
  • @Jarod42:const F&amp; f 也是如此。特别是,无论如何,任何操作都是在 f 上的副本上执行的。
【解决方案3】:

您可以使用第二个参数创建一个 func,然后将其添加到矩阵的每个成员中。

【讨论】:

  • 但是程序会变得非常非常慢。
  • @maple。使用&lt;vector&gt; 对性能的影响比参数参数更大。那个,而不是通过引用传递向量。
  • @maple:您使用的是什么编译器,您是否在两种情况下都验证过加法/减法实际上是执行的? (即打印原始矩阵和结果矩阵)
【解决方案4】:

你可以这样定义

template <template <class> class pred>
void apply(vector<vector<double>> & m) {
    for (auto & i : m)
        for (auto & j : i)
            j = pred<double>()(j, 1.0);
}

如需添加,请致电

apply<std::plus>(m);

同样,减法

apply<std::minus>(m);

你也可以使用&lt;functional&gt;中的其他函数对象

查看工作中的DEMO

【讨论】:

    【解决方案5】:

    您还可以为您的矩阵编写一个迭代器。这并不完整,但给了你一个想法:

    #include <vector>
    #include <algorithm>
    
    template< class _T >
    class Matrix
    {
    public:
        std::vector< std::vector< _T > > m_matrix;
        void alloc( size_t rows, size_t cols )
        {
            m_matrix.resize( rows );
            std::for_each( m_matrix.begin(), m_matrix.end(), [cols](std::vector<_T> &t){t.resize(cols);});
        }
        class iterator
        {
        public:
            iterator( std::vector< std::vector< _T > > & v ):
                m_vbegin( v.begin() ),
                m_vi( v.begin() ),
                m_i( m_vi->begin() ),
                m_vend( v.end() )
            {}
            iterator& operator++()
            {
                if ( ++m_i == m_vi->end() )
                {
                    if ( ++m_vi != m_vend )
                        m_i = m_vi->begin();
                }
                return (*this);
            }
            void gotobegin()
            {
                m_vi=m_vbegin;
                m_i=m_vi->begin();
            }
            void gotoend()
            {
                m_vi=m_vend;
            }
            _T & operator*()
            {
                return *m_i;
            }
            bool operator==(const iterator & rhs )
            {
                if ( m_vi==rhs.m_vi && (m_vi==m_vend || m_i==rhs.m_i) )
                    return true;
                return false;
            }
            bool operator!=(const iterator & rhs )
            {
                return !( *this == rhs );
            }
        private:
            typename std::vector< std::vector< _T > >::iterator m_vi;
            const typename std::vector< std::vector< _T > >::iterator m_vbegin;
            const typename std::vector< std::vector< _T > >::iterator m_vend;
            typename std::vector< _T >::iterator m_i;
        };
        iterator begin(){ return iterator(m_matrix); }
        iterator end(){ iterator ret(m_matrix); ret.gotoend(); return ret;}
    };
    int main( void )
    {
        Matrix<double> matrix;
        matrix.alloc( 4000, 4000 );
        std::for_each( matrix.begin(), matrix.end(), []( double &t){ t=1.0;} );
        std::for_each( matrix.begin(), matrix.end(), []( double &t){ t+=1.0;} );
        return 0;
    }
    

    【讨论】:

      【解决方案6】:

      除了其他答案。您也可以使用std::function(请参阅http://en.cppreference.com/w/cpp/utility/functional/function)。在这里,您必须定义返回类型以及参数类型。使用它,您可以传递任何可调用的内容。

      这是一个例子:

      #include <iostream>
      #include <functional>
      #include <vector>
      
      using matrix = std::vector<std::vector<double>>;
      
      void for_each_member(matrix& m, std::function<void(double&)> functor)
      {
        for (auto &vec : m)
          for (auto &element : vec)
            functor(element);
      }
      
      static void sub(double &ele)
      {
        --ele;
      }
      
      int main()
      {
        matrix m = { {1,2}, {3,4} };
      
        for_each_member(m, [](auto &ele) { ++ele; });
        for_each_member(m, [](auto &ele) {std::cout << ele << " "; });
      
        std::cout << "\n";
      
        for_each_member(m, sub);
        for_each_member(m, [](auto &ele) {std::cout << ele << " "; });
        std::cout << "\n";
      
        return 0;
      }
      

      【讨论】:

        【解决方案7】:

        免责声明:我打破了 vector 提供的一些 C++ 封装,以显示一个可能更快的解决方案,可以提供你想要的。一些在 SO 上使用 C++ 标签的纯粹主义者肯定会因为我向您展示一些 ahem C 代码而敲门。但无论如何我都会给你看。你可以自己做决定。

        inline void add_to_every_element(vector<double>* rows, size_t m_size, double d)
        {
            for (size_t i = 0; i < m_size; i++)
            {
                size_t row_size = rows[i].size();
                double* row = rows[i].data();
                for (size_t j = 0; j < row_size; j++)
                {
                    row[j] += d;
                }
            }
        }
        
        void add1(vector<vector<double> > &m)
        {
            add_to_every_element(m.data(), m.size(), 1.0);
        }
        
        void minus1(vector<vector<double>>& m)
        {
            add_to_every_element(m.data(), m.size(), -1.0);
        }
        

        【讨论】:

        • 谢谢你的回答,不过我还有很多其他的操作,除了+和-。
        • 然后使用 R. Sahu 的解决方案并根据需要进行优化。
        • 我不反对破坏抽象,如果它显着提高性能,但我想知道为什么你认为这可能更有效。
        • @MikeMB - 这是一个公平的问题。根据经验,我的假设是,我怀疑循环中多次调用 size()[] 运算符会生成大量代码。在发布版本中,很多这种开销只是被内联并且变得不那么成问题了。我的初始测试工具始终显示我的循环比 OP 的 large 矩阵(例如 100x100)快 10%。对于 较小的 3x3 矩阵,每个实现之间的发布版本的运行时间始终相同。很高兴离线讨论。
        • 感谢您的解释。我知道要避免在时间关键代码中重复调用 size(),但希望 operator[] 完全优化掉。无论如何:您是否尝试将其与 range-base-for-loop 进行比较?它给了我与您的解决方案完全相同的时间。
        猜你喜欢
        • 2013-11-16
        • 2013-10-07
        • 2016-12-07
        • 2012-03-08
        • 2012-02-09
        • 1970-01-01
        • 2023-02-10
        • 1970-01-01
        • 2010-09-29
        相关资源
        最近更新 更多