Showing posts with label c++. Show all posts
Showing posts with label c++. Show all posts

2009-06-28

程序治理

本文是对许老大的 一篇博文 的评论。

垃圾回收是 C++ 语言层不具备的一个功能, boost shared_ptr 在库层次实现了这个功能。对于这种超越语言功能的库,我的建议就是加强程序治理: 对使用这些库建立一些规则, 扬长避短。

  1. 对于同一个对象,要么完全使用 shared_ptr, 要么完全不使用 shared_ptr,原文提到的几个问题似乎都与这个相关。

  2. 对于一个类,在设计时就要决定这个类是否要以 shared_ptr 方法来使用。

  3. 对于需要用 shared_ptr 的类,建议采用如下的定义方式

    class _Foo {
    };
    typedef boost::shared_ptr<_Foo> Foo;
    Foo foo_new(constructor args...);




这套规则有一个变种(主要是为了解决 shared_ptr 的传播性问题): 限制 shared_ptr 在一个模块内部使用, 在模块的入口 API 处对需要返回的对象进行拷贝,以脱离 shared_ptr。

C 语言的 gobject 和 pyobject 库都有 GC 的支持,但解决起来似乎比 C++ 顺利很多,我觉得有如下的原因

  • 类和GC的概念是同时引入的, 而 C++ 的解决方案则拆成了两块

  • C++ 过于崇尚自由, 而 C 语言则已经在程序治理方面积累了很多经验

2009-03-17

尝试了一下直接写 gobject

先说结论吧: 1. 痛苦的体验,2. 用面向过程的语言来做面向对象的事时你需要处理很多繁琐的事情 3. 用 gob2 之类的工具可以改善体验。

基本结构


这部分有模板可以套(30-40行),你只需要把里面的名字换成自己的类的名字, 不过由于存在全大写, 首字母大写,用下划线分割的小写三种情况,所以你需要替换三次。

在这一步,你定义了 6 个宏,两个 struct 和两个函数 xxx_init 和 xxx_class_init.


处理析构


如果你的对象引用了其他对象,或者有动态分配的内存(比如字符串), 那么你就需要处理析构的问题。 gobject 为了解决循环引用的问题,所以析构分为两步, 第一步是解除对其他对象的引用(这一步叫做 dispose), 第二步是释放动态分配的内存(叫做 finialize)。在这一步中,你需要添加一个全局静态变量保存 parent_class, 定义 xxx_dispose 和 xxx_finalize (记得通过 parent_class 调用父类的对应的析构函数), 在 xxx_class_init 代码中初始化 parent_class, 已经把 xxx_dispose 和 xxx_finalize 关联到这个类。

私有成员


封装很重要,所以私有成员不应当放到头文件中,所以类 Xxx 的定义应该如下所示。

struct _Xxx {
GObject parent_instance;
XxxPrivate* priv;
}

gobject 用了一个小技巧来防止生成一个对象时分配两次内存,他可以让生成对象时申请的内存大小为 sizeof(Xxx) + sizeof(XxxPrivate),当然这个是可选的。

通常,你为了支持私有成员,你需要添加一个宏,同时在 xxx_init 和 xxx_class_init 中加入必要的初始化代码。

属性


gobject 的属性主要功能是为了实现事件监听,你可以在属性上挂回调函数来监听属性的改变。如果你不需要的监听的话,那么简单的一个私有成员,再加上 set 和 get 函数即可。实现属性是一件很郁闷的事,你首先要定义一个 enum 来给每个属性一个顺序号, 然后定义 xxx_get_property 和 xxx_set_property 来覆盖基类的实现,然后在 xxx_class_init 中为没个属性定义一个包含属性元数据的结构,最后依次用 g_object_class_install_property 安装。

仅有这些似乎还不够,你一般还需要定义一些辅助函数或辅助宏来降低用户使用的难度,包括 set, get, signal_connect 等。

信号


其实属性的大部分的功能都可以用信号来实现(在属性的 set 函数上 emit 一个 foo-changed 信号),而且 signal 的写法比较简单,无须重写 xxx_set_property 和 xx_get_property。

总之


总之,写的很烦,而且写了半天还没有写到业务代码,每增加一个属性或者信号都需要不少的工作量。看了一下 python 的 gobject 封装感觉很漂亮(见下), gob2 也不错,能让你开发得开心一点。


import gobject

class MyObject(gobject.GObject):

foo = gobject.property(type=str, default='bar')
boolprop = gobject.property(type=bool, default=False)

def __init__(self):
gobject.GObject.__init__(self)

@gobject.property
def readonly(self):
return 'readonly'



最后补一句,虽然 C 语言有些繁琐,但 C 平台真的非常成功,有空再补一下我对平台,库,语言三者关系的观点。

2008-12-28

sprintf/snprintf 陷阱

最近 ubuntu/debian 正在讨论 sprintf/snprintf 的问题,我在这描述一遍。

sprintf 的原型为:
int sprintf(char *restrict s, const char *restrict format, ...);

其中 char* restrict s 的含义为通过s 指向的内存空间不得与其他指针参数指向的内存的空间重叠。比如如下的语句就是错误的用法, 因为参数1与参数3指向的内存重叠了。但这种做法作为增强版的 strcat 已被广泛使用。

sprintf(buf, "%s foo %d %d", buf, var1, var2);

在 ubuntu 8.10 所带的 gcc 中,如果编译时加入了优化选项(比如 -O1, -O2), 那么sprinf 会首先将 s 清空,比如如下的程序会输出 "fail", 而不是 "not fail"。

#include
char buf[80] = "not ";
int main()
{
sprintf(buf, "%sfail", buf);
puts(buf);
return 0;
}






作为补救方案,可以使用如下的语句来代替:

sprintf(buf+strlen(buf), " foo %d %d", var1, var2);

snprintf 有类似的 bug.

很多 Debian/Ubuntu 包都有此 bug,可以参见如下两个 thread 中的具体内容

[1] https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/305901
[2] http://groups.google.com/group/linux.debian.devel/browse_thread/thread/c5bbda39f1a01bd4