免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 5748 | 回复: 21

[C++] C++成员函数作为普通C函数的函数指针 [复制链接]

论坛徽章:
5
戌狗
日期:2014-06-09 10:29:10酉鸡
日期:2014-12-01 16:05:27处女座
日期:2015-01-07 18:35:262015亚冠之水原三星
日期:2015-06-03 09:26:222015亚冠之布里斯班狮吼
日期:2015-06-15 10:53:54
发表于 2016-09-28 15:44 |显示全部楼层
class Base
{
    public:
        static void *hello_inn(void *){
            cout << "Hello, Base" << endl;
        }
        void create_thread()
        {
            pthread_t tid;
            pthread_create(&tid, NULL, hello_inn, NULL);
        }   
};

各位,C++的成员函数如何作为函数指针传递给C中的函数,如pthread_create,而又不使用static?

查了一些资料,都说是this指针的缘故,有没有一些深入一点的资料?

论坛徽章:
323
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
发表于 2016-09-28 15:57 |显示全部楼层
要借助一个普通函数转一下。
pthread_create的arg参数传对象指针,在函数中把参数强制转换成对象指针,调用对象的方法即可。

论坛徽章:
154
2022北京冬奥会纪念版徽章
日期:2015-08-07 17:10:5720周年集字徽章-年
日期:2022-10-26 16:44:2015-16赛季CBA联赛之深圳
日期:2022-11-02 14:02:4515-16赛季CBA联赛之八一
日期:2022-11-28 12:07:4820周年集字徽章-20	
日期:2023-07-19 08:49:4515-16赛季CBA联赛之八一
日期:2023-11-04 19:23:5115-16赛季CBA联赛之广夏
日期:2023-12-13 18:09:34
发表于 2016-09-28 16:17 |显示全部楼层
回复 2# hellioncu

指针的概念太复杂了,还有cpp的引用

底层代码就是头疼,要扣字眼

论坛徽章:
323
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
发表于 2016-09-28 16:33 |显示全部楼层
shang2010 发表于 2016-09-28 16:17
回复 2# hellioncu

指针的概念太复杂了,还有cpp的引用

相比新的那些c++特性,指针是浮云

论坛徽章:
154
2022北京冬奥会纪念版徽章
日期:2015-08-07 17:10:5720周年集字徽章-年
日期:2022-10-26 16:44:2015-16赛季CBA联赛之深圳
日期:2022-11-02 14:02:4515-16赛季CBA联赛之八一
日期:2022-11-28 12:07:4820周年集字徽章-20	
日期:2023-07-19 08:49:4515-16赛季CBA联赛之八一
日期:2023-11-04 19:23:5115-16赛季CBA联赛之广夏
日期:2023-12-13 18:09:34
发表于 2016-09-28 16:36 |显示全部楼层
回复 4# hellioncu

很多年不写cpp代码了,大好人给口饭吧

论坛徽章:
154
2022北京冬奥会纪念版徽章
日期:2015-08-07 17:10:5720周年集字徽章-年
日期:2022-10-26 16:44:2015-16赛季CBA联赛之深圳
日期:2022-11-02 14:02:4515-16赛季CBA联赛之八一
日期:2022-11-28 12:07:4820周年集字徽章-20	
日期:2023-07-19 08:49:4515-16赛季CBA联赛之八一
日期:2023-11-04 19:23:5115-16赛季CBA联赛之广夏
日期:2023-12-13 18:09:34
发表于 2016-09-28 16:37 |显示全部楼层
我感觉cpp的那些高级特性,全是在折腾编译器,我就稍微改一下语法,

测试一下输出结果,是我要的结果,这行代码就不准备相认了

论坛徽章:
323
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
发表于 2016-09-28 16:45 |显示全部楼层
shang2010 发表于 2016-09-28 16:36
回复 4# hellioncu

很多年不写cpp代码了,大好人给口饭吧

我们上海职位少,以实施、市场等为主

论坛徽章:
24
狮子座
日期:2013-12-31 10:48:0015-16赛季CBA联赛之吉林
日期:2016-04-18 14:43:1015-16赛季CBA联赛之北控
日期:2016-05-18 15:01:4415-16赛季CBA联赛之上海
日期:2016-06-22 18:00:1315-16赛季CBA联赛之八一
日期:2016-06-25 11:02:2215-16赛季CBA联赛之佛山
日期:2016-08-17 22:48:2615-16赛季CBA联赛之福建
日期:2016-12-27 22:39:272016科比退役纪念章
日期:2017-02-08 23:49:4315-16赛季CBA联赛之八一
日期:2017-02-16 01:05:3415-16赛季CBA联赛之山东
日期:2017-02-22 15:34:5615-16赛季CBA联赛之上海
日期:2017-11-25 16:17:5015-16赛季CBA联赛之四川
日期:2016-01-17 18:38:37
发表于 2016-09-28 17:45 |显示全部楼层
本帖最后由 zhujiang73 于 2016-09-28 17:58 编辑

回复 1# kaede_1

   c++ 成员函数指针和一般指针不一样,所以 pthread_create 处理不了,c++ 的线程可以这样写:

  1. #include <iostream>
  2. #include <thread>

  3. using namespace std;

  4. class Base
  5. {
  6.     public:
  7.         void hello_inn(){
  8.             cout << "Hello, Base" << endl;
  9.         }
  10. };


  11. int main(int argc, char** argv)
  12. {
  13.     Base *p_base = new Base();

  14.     std::thread  *p_thread = new std::thread(&Base::hello_inn, p_base);

  15.     p_thread->join();
  16. }
复制代码

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
发表于 2016-09-29 01:37 |显示全部楼层
哈哈,忽然想起我以前在逼乎上贴过的一个答案,讲C++11的thread包装是怎么做的。

绝大多数OS的thread入口函数大致是这样的:
  1. void_or_error_code entry_point(void *arbitrary_data);
复制代码

而std::thread的构造函数长这样:
  1. template< class Function, class... Args >
  2. explicit thread( Function&& f, Args&&... args );
复制代码

为了填平两者之间的差异,我们要做的就是把f和args统统打包在一起做成一个void *,然后用一个预定义的plain C function作为entry point,接收这个void *,解开其中的f和args,然后调用。

和某些回答的说法不同,OS thread API不能直接触碰function template,因为它们大都长这样(略去不相关的参数):
  1. error_code create_thread((void_or_error_code(*entry)(void *), void *data);
复制代码

不用折腾了,你想破头也没办法让这个API直接调用一个function template或者一个lambda,你只能给它提供一个plain function pointer。用std::function<void_or_error()>绕圈子是个办法,但到最后你还是需要把f和args打包在一起,然后再用一个包装函数拆包调用f(args...),然后再把这个函数包装在std::function里,我觉得反倒更麻烦了。

下面一步一步细说:

1、打包f和args成一个void *
当然我们不能真得把f和args直接变成一个void *,至少尺寸肯定不合适,所以我们需要一个数据结构来保存f和arg。另外,我们说过thread entry point必需是一个plain function,所以template戏法对于entry point来说是不能用的,为了让我们的entry point可以使用这些数据,我们需要一个concrete type而不是template。
不得不承认virtual function有时候还是有用的。让我们定义一个基类:
  1. struct thread_data_base
  2. {
  3.     virtual ~thread_data_base(){}
  4.     virtual void run()=0;
  5. };
复制代码

这个基类给我们提供了一个统一的入口,可以调用实际用户提供的f和args。

2、接下来我们定义一个template,以适配不同类型的f和args:
  1. template<typename F, class... ArgTypes>
  2. class thread_data : public thread_data_base
  3. {
  4. public:
  5.     thread_data(F&& f_, ArgTypes&&... args_)
  6.     : fp(std::forward<F>(f_), std::forward<ArgTypes>(args_)...)
  7.     {}

  8.     template <std::size_t... Indices>
  9.     void run2(tuple_indices<Indices...>)
  10.     { invoke(std::move(std::get<0>(fp)), std::move(std::get<Indices>(fp))...); }

  11.     void run() {
  12.         typedef typename make_tuple_indices<std::tuple_size<std::tuple<F, ArgTypes...> >::value, 1>::type index_type;
  13.         run2(index_type());
  14.     }
  15.    
  16. private:
  17.     /// Non-copyable
  18.     thread_data(const thread_data&)=delete;
  19.     void operator=(const thread_data&)=delete;
  20.     std::tuple<typename std::decay<F>::type, typename std::decay<ArgTypes>::type...> fp;
  21. };
复制代码

在这个template里有一个data member,它是一个tuple,用于保存f和args,这样我们就可以通过将void *data cast成thread_data_base *,然后调用其中的虚函数run来实际调用f(args...),里面的invoke和tuple_indices等下再说。

3、然后我们就可以用一个简单的函数把任意的f和args包装成一个thread_data_base *:
  1. template<typename F, class... ArgTypes>
  2. inline thread_data_base *make_thread_data(F&& f, ArgTypes&&... args)
  3. {
  4.     return new thread_data<typename std::remove_reference<F>::type, ArgTypes...>(std::forward<F>(f),
  5.                                                                                 std::forward<ArgTypes>(args)...);
  6. }
复制代码

4、thread entry point变得很简单,这样就行了:
  1. void_or_error_code thread_entry(void *data) {
  2.     std::unique_ptr<thread_data_base> p((thread_data_base *)data);
  3.     p->run();
  4.     // return result of p->run() if error code is required
  5. }
复制代码

异常处理等细节略去。

注意:下节含有大量C++黑魔法,可能会引起阅读者不适,请谨慎前行
5、下面看看invoke,或者说如何通过一个f和args组成的tuple调用f(args...)
简单说,对于一个tuple<F, T1, T2, T3> tp(f, a1, a2, a3),如果你想调用f(a1, a2, a3),你需要:
tp.get<0>()(tp.get<1>(), tp.get<2>(), tp.get<3>());
forward和decay神马的略去不提。
注意这里的1、2、3必须是编译期常数,否则你是没法拿来当template参数的,也就是说,为了调用f(args...),我们必需生成一个编译期的数列,这各编译期的数列就是前面提到的tuple_indices。
有了这个数列,假如它叫Indices,我们就可以这样调用:
  1. tp.get<0>()(tp.get<Indices>()...);
复制代码

make_tuple_indices就是用来生成Indices的。
为了生成数列[Sp, Ep),我们要做的就是从Sp开始,递归的在已有数列后面加一项,直到满足条件(Sp==Ep)。
让我们先定义tuple_indices:
  1. template <std::size_t...> struct tuple_indices {};
复制代码

这个类不需要任何成员,所有需要的信息,也就是整个数列,都是template参数,是类型的一部分。
为了让代码清楚一点,我们再绕个圈子:
  1. template <std::size_t Sp, class IntTuple, std::size_t Ep> struct make_indices_imp;
复制代码

之所以要绕这个圈子,是因为我们的make_tuple_indices应该长这样:
  1. template <std::size_t Ep, std::size_t Sp>
  2. struct make_tuple_indices {...};
复制代码

但在生成这个数列的过程中,为了方便,我们想把数列当前项直接放在参数列表里,要不然还需要在内部找到数列的最后一项,太烦。
这个make_tuple_indices_imp完工后是这个样子的:
  1. template <std::size_t Sp, class IntTuple, std::size_t Ep> struct make_indices_imp;

  2. template <std::size_t Sp, std::size_t... Indices, std::size_t Ep>
  3. struct make_indices_imp<Sp, tuple_indices<Indices...>, Ep>
  4. { typedef typename make_indices_imp<Sp+1, tuple_indices<Indices..., Sp>, Ep>::type type; };

  5. template <std::size_t Ep, std::size_t... Indices>
  6. struct make_indices_imp<Ep, tuple_indices<Indices...>, Ep>
  7. { typedef tuple_indices<Indices...> type; };
复制代码

可以看到有三个版本,第一个是泛化形式,第二个是递归中间结果,第三个是递归终止条件(Sp==Ep)
然后我们就可以把make_tuple_indices写出来了:
  1. template <std::size_t Ep, std::size_t Sp=0>
  2. struct make_tuple_indices {
  3.     typedef typename make_indices_imp<Sp, tuple_indices<>, Ep>::type type;
  4. };
复制代码

注意为了方便起见,Ep在前面,Sp在后面,因为缺省参数必须在最后(没办法C++就是这么规定的)
invoke可以很简单,就是把所有东西都forward过去调f:
  1. template <class Fp, class... Args>
  2. inline auto invoke(Fp&& f, Args&&... args)
  3. -> decltype(std::forward<Fp>(f)(std::forward<Args>(args)...))
  4. { return std::forward<Fp>(f)(std::forward<Args>(args)...); }
复制代码

当然,为了应付不同的callable,比如成员函数指针,或者有operator()的类,或者lambda什么的,我们可能还需要几个特化,不过这些都不重要,这里就不说了。

6、有了上面这些东西,之前的thread_data::run/run2就能运转了,它能通过make_tuple_indices和invoke解包tuple并调用f(args...)。
我们已经有了run,之所以需要再定义一个run2,是因为一条可能很多人猛一下想不起来的C++语法规定——member function template不能是虚函数。
Indices是一个template type,只能用一个template function接收,所以我们需要把run和run2拆开,run作为继承下来的虚函数做入口,run2接收Indices并用之前提到的方法调用f(args...)。

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
发表于 2016-09-29 08:36 |显示全部楼层
回复 9# windoze

被 Haskell 黑魔法虐过之后,回头再看看 C++ 黑魔法,发现我整个人都平静了很多……
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP