【问题标题】:C++: function that works with container and container of pointers as wellC ++:也适用于容器和指针容器的函数
【发布时间】:2022-10-20 22:37:21
【问题描述】:

我想我正面临着一些我认为在这里很常见的问题。 我想编写一个函数,它既可以接受对象容器(比如说std::vector),又可以接受指向这些对象的指针容器。

这样做的正确方法是什么?

此刻,我在想

int sum(std::vector<int *> v)
{
  int s = 0;
  for (int * i : v) s += *i;
  return s;
}

int sum(std::vector<int> v)
{
  std::vector<int *> vp;
  for (size_t i = 0; i < v.size(); ++i)
    vp[i] = &v[i];
  return sum(vp);
}

但这似乎不太正确,不是吗?

【问题讨论】:

  • 我会做相反的事情。使用法线向量求和,必要时从基于指针的转换。如果它的法线向量,这将具有更高的性能。我的意思是,如果只是求和,则无需添加重定向层。

标签: c++ pointers vector prototype function-prototypes


【解决方案1】:

考虑标准算法库,您看到的问题有解决方案。

大多数算法都有一些默认行为,但通常允许您通过函子参数自定义该行为。

对于您的具体情况,选择的算法是std::accumulate

因为这个算法已经存在,我可以在这里限制为一个相当简化的说明:

#include <iostream>
#include <functional>

template <typename T,typename R,typename F = std::plus<>>
R sum(const std::vector<T>& v,R init,F f = std::plus<>{})
{  
  for (auto& e : v) init = f(init,e);
  return init;
}

int main() {
    std::vector<int> x{1,2,3,4};
    std::vector<int*> y;
    for (auto& e : x ) y.push_back(&e);

    std::cout << sum(x,0)  << "
";
    std::cout << sum(y,0,[](auto a, auto b) {return a + *b;});

}

std::plus 是一个将两个值相加的函子。因为返回类型可能与向量元素类型不同,所以使用了额外的模板参数R。类似于std::accumulate,这是从作为参数传递的初始值推导出来的。添加int 时,默认std::plus&lt;&gt; 很好。当添加指针指向的整数时,仿函数可以将累加器与取消引用的向量元素相加。如前所述,这只是一个简单的玩具示例。在上面的链接中,您可以找到std::accumulate 的可能实现(它使用迭代器而不是直接使用容器)。

【讨论】:

  • 感谢您的回答。 sum 实际上只是一个非常简单的示例,用于更复杂的功能。这里的主要问题是您能否在不复制代码或复制一个或另一个向量的情况下同时拥有这两个功能
  • @ben 我只能回答您发布的问题。对于更复杂的算法,同样适用。不要对元素上的操作进行硬编码,使用一些带有默认值的仿函数,调用者可以自定义它
【解决方案2】:

使用 C++20(或其他范围库),您可以轻松添加或删除指针

template <std::ranges::range R, typename T>
concept range_of = requires std::same<std::ranges::range_value_t<R>, T>;

template <range_of<int *> IntPointers>
int sum_pointers(IntPointers int_pointers)
{
    int result = 0;
    for (int * p : int_pointers) result += *p;
    return result;
}

void call_adding_pointer()
{
    std::vector<int> v;
    sum_pointers(v | std::ranges::views::transform([](int & i){ return &i; });
}

或者

template <range_of<int> Ints>
int sum(Ints ints)
{
    int result = 0;
    for (int i : ints) result += i;
    return result;
}

void call_removing_pointer()
{
    std::vector<int *> v;
    sum(v | std::ranges::views::transform([](int * p){ return *p; });
}

【讨论】:

    【解决方案3】:

    您可以制作一个函数模板,它对指针和非指针的行为不同:

    #include <iostream>
    #include <vector>
    using namespace std;
    
    template <class T>
    auto sum(const std::vector<T> &vec)
    {
        if constexpr (std::is_pointer_v<T>)
        {
            typename std::remove_pointer<T>::type sum = 0;
            for (const auto & value : vec) sum += *value;
            return sum;
        }
        if constexpr (!std::is_pointer_v<T>)
        {
            T sum = 0;
            for (const auto & value : vec) sum += value;
            return sum;
        }
    }
    
    int main(){
        std::vector<int> a{3, 4, 5, 8, 10};
        std::vector<int*> b{&a[0], &a[1], &a[2], &a[3], &a[4]};
        cout << sum(a) << endl;
        cout << sum(b) << endl;
    }
    

    https://godbolt.org/z/sch3KovaK

    您可以将几乎所有内容移出 if constexpr 以减少代码重复:

    template <class T>
    auto sum(const std::vector<T> &vec)
    {
        typename std::remove_pointer<T>::type sum = 0;
        for (const auto & value : vec) 
        {
            if constexpr (std::is_pointer_v<T>)
                sum += *value;
            if constexpr (!std::is_pointer_v<T>)
                sum += value;
        }
        return sum;
    }
    

    https://godbolt.org/z/rvqK89sEK

    【讨论】:

      【解决方案4】:

      基于@mch 解决方案:

      
      template<typename T>
      std::array<double, 3> center(const std::vector<T> & particles)
      {
          if (particles.empty())
              return {0, 0, 0};
      
          std::array<double, 3> cumsum = {0, 0, 0};
      
          if constexpr (std::is_pointer_v<T>)
          {
              for (const auto p : particles)
              {
                  cumsum[0] += p->getX();
                  cumsum[1] += p->getY();
                  cumsum[2] += p->getZ();
              }
          }
          if constexpr (not std::is_pointer_v<T>)
          {
              for (const auto p : particles)
              {
                  cumsum[0] += p.getX();
                  cumsum[1] += p.getY();
                  cumsum[2] += p.getZ();
              }
          }
          double f = 1.0 / particles.size();
          cumsum[0] *= f;
          cumsum[1] *= f;
          cumsum[2] *= f;
          return cumsum;
      }
      

      使用std::invoke 的更清洁、更高效的解决方案:

      std::array<double, 3> centroid(const std::vector<T> & particles)
      {
          if (particles.empty())
              return {0, 0, 0};
      
          std::array<double, 3> cumsum{0.0, 0.0, 0.0};
          for (auto && p : particles)
          {
              cumsum[0] += std::invoke(&topology::Particle::getX, p);
              cumsum[1] += std::invoke(&topology::Particle::getY, p);
              cumsum[2] += std::invoke(&topology::Particle::getZ, p);
          }
      
          double f = 1.0 / particles.size();
          cumsum[0] *= f;
          cumsum[1] *= f;
          cumsum[2] *= f;
          return cumsum;
      }
      

      【讨论】:

      • const auto p : particles ==> const auto &amp;p : particles 不复制元素。
      • 是的……完全正确
      • 这是累加然后除以结果,你需要std::array&lt;double, 3&gt; plus(std::array&lt;double, 3&gt;, particle)(和particle *
      • 我肯定会对你如何使用仿函数函数来做这件事感兴趣。给你godbolt.org/z/xs76jdc99。 (仅缺少最后的除法)它只是我在答案中写的应用于particles 累积在array 而不是添加ints
      • 顺便说一句,回答您自己的问题完全可以,但是如果您在问题中发布简化代码,那么答案应该是指该代码,而不是您未包含在问题中的未简化代码。也许这就是你真正想要的,但根据你的问题,没有人能想出这个
      猜你喜欢
      • 2017-04-11
      • 1970-01-01
      • 1970-01-01
      • 2010-09-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多