免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12
最近访问板块 发新帖
楼主: xpycc
打印 上一主题 下一主题

[C++] operator ->* 问题 [复制链接]

论坛徽章:
0
11 [报告]
发表于 2009-08-20 08:02 |只看该作者
to 9 楼:

您说的没错,不过当时我的思路有些乱了,没讲清楚,请见谅。

其实我的意思是,为什么 operator ->* 不能设计成一元的呢?就像 operator -> 一样。

对于 operator -> 来说,它最直接的应用自然是所谓的代理句柄了。比如:

  1. template <class T>
  2. class Handle {
  3.         T* _p;
  4.         Handle& operator = (Handle&);
  5. public:
  6.         Handle(T* p = new T()): _p(p) {}
  7.         operator T* () { return _p; }
  8.         T* operator -> () { return _p; }
  9.         ~Handle() { delete _p; }
  10. };
复制代码

这是一个简单的示例。正像您说的“函数指针和数据指针向来都是被区别对待的”。显然,这里的 T 绝对不会是像 void(void) 这样的函数类型,因为根本没有办法对函数进行 new 操作。这是句柄工作的一个前提,也是能写出句柄类的重要假设。

那么对于 Handle<ConcreteType> 的一个实例 h 来说,以下的表达式都能被转换:

  1. *h; h->md; h->*pmd; h->mf(); h->*pmf();
  2. // 转换成
  3. * h.operator ConcreteType* ();
  4. h.operator -> () ->md;
  5. h.operator ConcreteType* () ->*pmd;
  6. h.operator -> () ->mf();
  7. h.operator ConcreteType* () ->*pmf();
复制代码

显然,operator -> 和 operator ->* 有很多共性,假如 operator ->* 是一元的,那么再和 operator * 配合之后,就不必使用有可能会出现一些微妙错误的类型转换操作符 operator ConcreteType* 了。那么应该就是这样的:

  1. h->*pmd; h->*pmf();
  2. // 转换成(目前不可实现)
  3. h.operator ->* () ->*pmd;
  4. h.operator ->* () ->*pmf();
复制代码

看起来,如果 operator ->* 是一元的,那么事情就会精确地多。然而 C++ 标准说了,operator ->* 是二元的。那么其中的原因是什么呢?其实也很容易想到,->* 表达式是对于两个指针的操作符,既然是指针就可能是句柄,那么为什么右操作数就不能是句柄呢?哦,或许右操作数也可能是句柄。

那么事实真的如此吗?答案或许是否定的,就像 void(void) 不能进行 new 操作一样,成员也是不能进行 new 操作的,无论数据成员指针还是成员函数指针都是如此,因为从逻辑上讲,成员是不能独立于整体存在的。那么对于数据成员指针,有可能写出它的句柄类吗?答案是肯定的,然而这样做或许是没有意义的。首先,句柄一般是用来管理存储或者资源的分配与释放的。既然数据成员不能单独存在,存储或资源的分配与释放就无从谈起。所以,为数据成员写句柄类几乎没有意义。而且作为姊妹的 .* 表达式就不存在 operator .* ,也说明了其实根本就不需要存在数据成员的句柄类。

那么至少在表达内置结构的代理意义这方面,operator ->* 做成是一元的话就没有多大问题。然而事实是 operator ->* 并没有像 operator -> 那样做成一元的,虽然,也许标准委员会有另外的想法。

那么问题出在哪里了呢?正像您说的“这漏洞从C时代就开始了”。在 C 中,指针的概念是十分模糊的,它仅有指向某一个内存地址的抽象概念。在 C/C++ 中,指针几乎是万能的,它可以是一个句柄(资源的持有者),可以是一个数组(指向数组的首地址),也可以是一个函数(指向函数的地址)……以及以上概念的组合。而从来就没有细化这些概念。或许,在使用的时候,这样很方便,可以少敲几个字符,而且很多时候我们已经习惯了这样的方式。然而,这正是 C 类型系统混乱的一个侧面(当然,经过一些努力,现代 C 是要好多了,请别忘记,K&R C 那个时候,函数调用竟然不用检查参数类型)。因此 C++ 想在这样的基础上前进,才会显得步履维艰。


to 10 楼:

确实,仿函数就可以看成一个闭包。以前我曾尝试在 C++ 类型系统中实现函数的柯里化,使用的就是仿函数,可惜不太成功,不具有一般性。

C/C++ 的类型系统确实不完备,然而完全完备的系统似乎并不存在(歌德尔不完备性)。类型系统比较完备的当然要数纯函数式语言了,在这些语言中只有函数这个类型概念,既简化了类型的概念,又能对其进行强化。用这些语言写的程序,几乎通过了编译,运行时就是正确的,其中,类型系统功不可没。

然而 C++ 的一些问题似乎在于 C++ 太过于强调类型统一(比如在 C 中,必须写 struct S ,而在 C++ 中,可以只写 S ,因为 C++ 希望对所有类型一视同仁)。以至于必须考虑到所有可能的特殊情况,如果不小心遗漏了一些,就有可能出问题。这集中地体现在泛型编程上。

关于更多地使用哪种语言的问题,其实 C 和 C++ 的能力是差不多的(不考虑代码量),除非在 ABI 要求特别严格的地方或者极端环境下,我不会使用 C 来进行 P 或 OOP(尽管 C 上也可以进行 OOP)。当然,如果不要求极端的数值计算速度,我也不会用 Fortran 。对于那些 DSL 也是一样,除非情况特殊,一般我都会尽量使用通用语言。我认为这更多地取决于个人喜好和工作环境,和语言本身的关系不大。

[ 本帖最后由 xpycc 于 2009-8-20 08:23 编辑 ]

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
12 [报告]
发表于 2009-08-20 13:29 |只看该作者
说的很对,不过要纯函数式语言能通过编译实在是太难了,我想学Haskell最后放弃的人一半是因为看不懂编译错误的提示吧……

如果有本书叫做Haskell源码剖析或者《Inside Hashell Object Model》之类的就爽了,我还是喜欢用自己完全知道其内部原理的语言。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
13 [报告]
发表于 2009-08-20 13:33 |只看该作者

回复 #11 xpycc 的帖子

我看到第3段代码就不明就里了
你说得很清楚,  只是我反应有点慢……
我有空再看看~~~


关于这个,我很感兴趣:
原帖由 xpycc 于 2009-8-20 08:02 发表
以前我曾尝试在 C++ 类型系统中实现函数的柯里化,使用的就是仿函数,可惜不太成功,不具有一般性。


为了讨论严谨一点, 先将两个经常混淆的概念区分一下:
是实现函数的柯里化(currying)?  还是部分应用(partial application)?

论坛徽章:
0
14 [报告]
发表于 2009-08-20 14:10 |只看该作者
to 12 楼:

这就说明了类型系统的必要性

ps. 您说得对,Haskell 是属于那种拓宽下视野的语言,而不是用来长期工作的。不精通内部原理,同样复杂度的算法,不同实现有可能会慢好几倍

to 13 楼:

不算操作符,仅对仿函数(厄……我直接把这个当函数看了……)而言,仅仅能对常数个参数起作用,比如:
add (1) (2) (3)
add (1, 2) (3)
add (1) (2, 3)
add (1, 2 ,3)
其中 add (1) 这样的返回一个可以接受两个参数的仿函数实例(说白了也就是个闭包)。

后来有牛人提出仅保留 add (1) (2) (3) 这样的形式,不过由于时间关系,还没有实现过,从此一直搁浅至今……

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
15 [报告]
发表于 2009-08-20 14:25 |只看该作者
是说这样么?

  1. #include <iostream>

  2. class add
  3. {
  4. public:
  5.     explicit add(int value = 0) : value_(value) {}

  6.     add operator()(int rhs)
  7.     {
  8.         value_ += rhs;
  9.         return *this;
  10.     }

  11.     operator int()
  12.     {
  13.         return value_;
  14.     }

  15.     int value_;
  16. };

  17. int main(void)
  18. {
  19.     using namespace std;

  20.     cout << add(1)(2)(3) << endl;
  21. }

复制代码


另外从效率上来说,operator()返回引用好像也可以,暂时没发现坏处。

[ 本帖最后由 starwing83 于 2009-8-20 14:26 编辑 ]

论坛徽章:
0
16 [报告]
发表于 2009-08-20 14:38 |只看该作者

回复 #15 starwing83 的帖子

比这个要再一般一些……

ps1. 我那个好像 add (1) 和 add (1) (2) 的类型是不同的,而且限定参数个数为常数个。(因为那个时候没有看到 Text.Printf 这个东西)

ps2. 补充下 14 楼的帖子,个人认为,调试 C++ 的 TMP 和调试 Haskell 所面临的囧境差不多

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
17 [报告]
发表于 2009-08-20 15:18 |只看该作者
话说,TMP的地位其实和Haskell差不多,都很阳春。

刚刚发现是tr1还是C++0x,开始支持可变参数的模板了……Orz,这样弄个printf的curry就简单多了……不过我还是不会。

有没有办法弄个可以curry化的,类型安全的printf出来?貌似很好玩。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP