【问题标题】:how can i find a beautiful function wrapper我怎样才能找到一个漂亮的函数包装器
【发布时间】:2021-06-15 07:56:48
【问题描述】:
typedef void (*void_proc)(void* parameter); 
void* parallel_init(void* dummy, int core_number);
int parallel_addtask(void* parallel_monitor, void_proc process, void *parameter);
int parallel_waittask(void* parallel_monitor, int task_id);
int parallel_uninit(void* parallel_monitor);

struct parallel_parameter {
    int end;
    int begin;
};

void process(void* parameter) {
    auto p = reinterpret_cast<parallel_parameter*>(parameter);
    // ur_function_name(p->begin, p-end);
}

上面是我喜欢使用的并行库(c 风格)。每次你调用它时,你都应该定义一个特定的结构参数,这太烦人了,我想实现一个模板函数来减轻调用步骤,我尝试了一些方法来实现这一点,但失败了。


template<typename _function, typename... _parameter>
int parallel_executor(_function&& function, _parameter&&... parameter) {
    auto res = 0;
    parallel_parameter p[8]{0};
    auto body = [](void* para) -> void {
        auto p = reinterpret_cast<parallel_parameter*>(para);
        function(p->begin, p->end, std::forward<_parameter>(parameter)...)
    };
    auto parallel_handle = parallel_init(nullptr, 8);
    do {
        for (int i = 0;i < 8; ++i) {
            res = parallel_addtask(parallel_handle, body, static_cast<void*>(&p[i]));
            if (res != 0) break;
        }
        for (int i = 0; i < 8; ++i) {
            res = parallel_waittask(parallel_handle, i);
            if (res != 0) break;
        }
    } while (false);
    parallel_uninit(parallel_handle);
    return res;
}

这个调用只是简单地显示我的困境,当我使用parallel_executor时,结果session无法访问,因为我不具体捕获样式,但是当我将主体更改为以下样式时, parallel_addtask 将不接受正文函数。

auto body = [&](void* para) -> void {
    auto p = reinterpret_cast<parallel_parameter*>(para);
    function(p->begin, p->end, std::forward<_parameter>(parameter)...)
};

现在我处于这种尴尬的境地有一段时间了。下面是我喜欢的通话方式。

auto ret = parallel_executor(
    [](int begin, int end, int parameter_1, int parameter_2) {
        std::cout << begin << " ==> " << end << " ==> " << parameter_1 << std::endl;
    },
    100, // parameter_1
    200  // parameter_2
);

关于这个问题,我希望我已经说清楚了。任何建议表示赞赏。

【问题讨论】:

  • 第一个参数不是函数,所以 bind 使用不正确 afaik。 Lambda 表达式生成唯一类类型的对象
  • beginend参数从何而来?它是如何工作的? dummy 是如何发挥作用的? static_cast&lt;void*&gt;(&amp;p) 您是否将指向在循环块范围内的堆栈上创建的结构的指针传递给并行运行的函数?!该函数是否复制数据?
  • parameter 的预期生命周期是多少? (你在parallel_waittask 之前停下来,这对我来说似乎很可疑:/)
  • @KamilCuk 是的,你是对的,我将 stack variable 传递给函数,这是危险的。我编写示例代码只是为了说明我的情况,我会在空闲时间修复它。
  • @Jarod42 是的,这很危险。我会修复这个错误。

标签: c++ pthreads c++17


【解决方案1】:

包装器可能看起来像:

class ParrallelWrapper
{
public:
    ParrallelWrapper(int core_number) :
        parallel_monitor(parallel_init(nullptr, core_number))
    {}

    ParrallelWrapper(const ParrallelWrapper&) = delete;
    ParrallelWrapper& operator= (const ParrallelWrapper&) = delete;

    ~ParrallelWrapper() { parallel_uninit(parallel_monitor); }

    int AddTask(std::function<void()> f) {
        auto run_function = *[](void* f){
            (*reinterpret_cast<std::function<void()>*>(f))();
        };
        functions.push_back(std::make_unique<std::function<void()>>(f));
        return parallel_addtask(parallel_monitor, run_function, functions.back().get());
    }
    int Wait(int task_id) { return parallel_waittask(parallel_monitor, task_id); }
private:
    void* parallel_monitor = nullptr;

    // Ensure lifetime, and pointer consistence.
    std::vector<std::unique_ptr<std::function<void()>>> functions;
};

Demo

【讨论】:

    【解决方案2】:

    使用适当的空格来指定beginend 和任务数,您可以使用类似

    struct parallel_deleter {
      void operator()(void *m) const {parallel_uninit(m);}
    };
    
    template<class F,class ...TT>
    int parallel_executor(F f,TT &&...tt) {
      constexpr auto p=+f;          // require captureless
      constexpr int n=/*...*/;
      std::unique_ptr<void,parallel_deleter> m(parallel_init(nullptr,n));
      struct arg {
        int begin,end;
        std::tuple<TT...> user;
      };
      std::vector<arg> v(n,{0,0,{tt...}});
      for(auto &x : v) {
        x.begin=/*...*/;
        x.end=/*...*/;
        if(const int res=parallel_addtask(m.get(),[](void *v) {
            const auto &a=*static_cast<arg*>(v);
            std::apply([&a](auto &...aa) {p(a.begin,a.end,aa...);},a.user);
          },&x)) return res;
      }
      for(int i=0;i<n;++i)
        if(const int res=parallel_waittask(m.get(),i)) return res;
      return parallel_uninit(m.release());
    }
    

    此设计依赖于传递的无捕获 lambda(因此p 可以在任务 lambda 中使用而无需捕获任何内容);如果您需要支持任何可调用的,Jarod42 基于std::function 的解决方案更胜一筹。

    【讨论】:

    • 谢谢@Davis Herring,这部分好像有问题c++ std::apply([&amp;a](auto &amp;...aa) {p(a.begin,a.end,aa...);},a.user);
    • @degawong:什么“问题”?我对其进行了测试,尽管对 parallel_* 函数的定义很简单。
    • @Davis Herringparallel_addtask的第二个参数类型必须是void(void*),当我们传递一个lambda给它时,lambda应该是无捕获的,如果@987654336 @ 是无捕获的,我认为我们不能将p 用于lambda。我的编译环境是MSVC 2019 16.10
    • @degawong:我认为这是一个编译器错误;您可以尝试使用static 来解决它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-02
    • 2011-06-03
    • 2015-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多