longrenle

积累,进步,成长

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  12 随笔 :: 0 文章 :: 2 评论 :: 0 引用

公告

2012年5月14日 #

Doubango 刚刚推出了“世界上第一个Html5 SIP客户端”:SipML5,实现了基于Chrome的SIP客户端,并与自己先前的开源产品Idoubs和IMSDroid实现互通。就像主页里的两个Demo视频显示的一样,你可以轻松实现Chrome和IOS/Android移动设备之间的实时视音频通话。

SipML5使用Chrome的实验功能WebRTC实现媒体功能,并用Javascript封装了一个完整强大的Javascript SIP/SDP stack 完成信令的管理,传输层通过Websocket与服务端Gateway通信接入SIP Server,最终通过Video TAG播放视音频内容。

于是:

Html5 + Websocket + Javascript SIP/SDP stack + WebRTC Media Stack

=> World's first HTML5 SIP client

大家可以去体验一下喽~ http://www.sipml5.org/

posted @ 2012-05-14 23:34 longrenle 阅读(129) 评论(0) 编辑

2012年4月10日 #

正则表达式用途甚广,各种语言脚本都兼容标准的正则表达式,下面总结基本符号和语法,做复习备忘之用。

元字符

表1.常用的元字符
代码说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

重复

表2.常用的限定符
代码/语法说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

字符类

我们可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于\w(如果只考虑英文的话)。

分枝条件

分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|“把不同的规则分隔开。

Example:0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)

分组

可以用小括号“()”来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。

反义

表3.常用的反义代码
代码/语法说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

例子:\S+匹配不包含空白符的字符串

<a[^>]+>匹配用尖括号括起来的以a开头的字符串

 

 

posted @ 2012-04-10 16:37 longrenle 阅读(11) 评论(0) 编辑

2012年3月27日 #

众所周知,多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。C++ 和 Java 作为当前最为流行的两种面向对象编程语言,其内部对于多态的支持到底是如何实现的呢,本文对此做了全面的介绍。

注意到在本文中,指针和引用会互换使用,它们仅是一个抽象概念,表示和另一个对象的连接关系,无须在意其具体的实现。

Java 的实现方式

Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用和接口引用调用的实现则有所不同。总体而言,当某个方法被调用时,JVM 首先要查找相应的常量池,得到方法的符号引用,并查找调用类的方法表以确定该方法的直接引用,最后才真正调用该方法。以下分别对该过程中涉及到的相关部分做详细介绍。

JVM 的结构

典型的 Java 虚拟机的运行时结构如下图所示


图 1.JVM 运行时结构
图 1.JVM 运行时结构 

此结构中,我们只探讨和本文密切相关的方法区 (method area)。当程序运行需要某个类的定义时,载入子系统 (class loader subsystem) 装入所需的 class 文件,并在内部建立该类的类型信息,这个类型信息就存贮在方法区。类型信息一般包括该类的方法代码、类变量、成员变量的定义等等。可以说,类型信息就是类的 Java 文件在运行时的内部结构,包含了改类的所有在 Java 文件中定义的信息。

注意到,该类型信息和 class 对象是不同的。class 对象是 JVM 在载入某个类后于堆 (heap) 中创建的代表该类的对象,可以通过该 class 对象访问到该类型信息。比如最典型的应用,在 Java 反射中应用 class 对象访问到该类支持的所有方法,定义的成员变量等等。可以想象,JVM 在类型信息和 class 对象中维护着它们彼此的引用以便互相访问。两者的关系可以类比于进程对象与真正的进程之间的关系。

Java 的方法调用方式

Java 的方法调用有两类,动态方法调用与静态方法调用。静态方法调用是指对于类的静态方法的调用方式,是静态绑定的;而动态方法调用需要有方法调用所作用的对象,是动态绑定的。类调用 (invokestatic) 是在编译时刻就已经确定好具体调用方法的情况,而实例调用 (invokevirtual) 则是在调用的时候才确定具体的调用方法,这就是动态绑定,也是多态要解决的核心问题。

JVM 的方法调用指令有四个,分别是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前两个是静态绑定,后两个是动态绑定的。本文也可以说是对于 JVM 后两种调用实现的考察。

常量池(constant pool)

常量池中保存的是一个 Java 类引用的一些常量信息,包含一些字符串常量及对于类的符号引用信息等。Java 代码编译生成的类文件中的常量池是静态常量池,当类被载入到虚拟机内部的时候,在内存中产生类的常量池叫运行时常量池。

常量池在逻辑上可以分成多个表,每个表包含一类的常量信息,本文只探讨对于 Java 调用相关的常量池表。

CONSTANT_Utf8_info

字符串常量表,该表包含该类所使用的所有字符串常量,比如代码中的字符串引用、引用的类名、方法的名字、其他引用的类与方法的字符串描述等等。其余常量池表中所涉及到的任何常量字符串都被索引至该表。

CONSTANT_Class_info

类信息表,包含任何被引用的类或接口的符号引用,每一个条目主要包含一个索引,指向 CONSTANT_Utf8_info 表,表示该类或接口的全限定名。

CONSTANT_NameAndType_info

名字类型表,包含引用的任意方法或字段的名称和描述符信息在字符串常量表中的索引。

CONSTANT_InterfaceMethodref_info

接口方法引用表,包含引用的任何接口方法的描述信息,主要包括类信息索引和名字类型索引。

CONSTANT_Methodref_info

类方法引用表,包含引用的任何类型方法的描述信息,主要包括类信息索引和名字类型索引。


图 2. 常量池各表的关系
图 2. 常量池各表的关系 

可以看到,给定任意一个方法的索引,在常量池中找到对应的条目后,可以得到该方法的类索引(class_index)和名字类型索引 (name_and_type_index), 进而得到该方法所属的类型信息和名称及描述符信息(参数,返回值等)。注意到所有的常量字符串都是存储在 CONSTANT_Utf8_info 中供其他表索引的。

方法表与方法调用

方法表是动态调用的核心,也是 Java 实现动态调用的主要方式。它被存储于方法区中的类型信息,包含有该类型所定义的所有方法及指向这些方法代码的指针,注意这些具体的方法代码可能是被覆写的方法,也可能是继承自基类的方法。

如有类定义 Person, Girl, Boy,


清单 1

				
 class Person { 
 public String toString(){ 
    return "I'm a person."; 
	 } 
 public void eat(){} 
 public void speak(){} 
	
 } 

 class Boy extends Person{ 
 public String toString(){ 
    return "I'm a boy"; 
	 } 
 public void speak(){} 
 public void fight(){} 
 } 

 class Girl extends Person{ 
 public String toString(){ 
    return "I'm a girl"; 
	 } 
 public void speak(){} 
 public void sing(){} 
 } 

 

当这三个类被载入到 Java 虚拟机之后,方法区中就包含了各自的类的信息。Girl 和 Boy 在方法区中的方法表可表示如下:


图 3.Boy 和 Girl 的方法表
图 3.Boy 和 Girl 的方法表 

可以看到,Girl 和 Boy 的方法表包含继承自 Object 的方法,继承自直接父类 Person 的方法及各自新定义的方法。注意方法表条目指向的具体的方法地址,如 Girl 的继承自 Object 的方法中,只有 toString() 指向自己的实现(Girl 的方法代码),其余皆指向 Object 的方法代码;其继承自于 Person 的方法 eat() 和 speak() 分别指向 Person 的方法实现和本身的实现。

Person 或 Object 的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。

如调用如下:


清单 2

				
 class Party{ 
…
 void happyHour(){ 
 Person girl = new Girl(); 
 girl.speak(); 
…
	 } 
 } 

 

当编译 Party 类的时候,生成 girl.speak()的方法调用假设为:

Invokevirtual #12

设该调用代码对应着 girl.speak(); #12 是 Party 类的常量池的索引。JVM 执行该调用指令的过程如下所示:


图 4. 解析调用过程
图 4. 解析调用过程 

JVM 首先查看 Party 的常量池索引为 12 的条目(应为 CONSTANT_Methodref_info 类型,可视为方法调用的符号引用),进一步查看常量池(CONSTANT_Class_info,CONSTANT_NameAndType_info ,CONSTANT_Utf8_info)可得出要调用的方法是 Person 的 speak 方法(注意引用 girl 是其基类 Person 类型),查看 Person 的方法表,得出 speak 方法在该方法表中的偏移量 15(offset),这就是该方法调用的直接引用。

当解析出方法调用的直接引用后(方法表偏移量 15),JVM 执行真正的方法调用:根据实例方法调用的参数 this 得到具体的对象(即 girl 所指向的位于堆中的对象),据此得到该对象对应的方法表 (Girl 的方法表 ),进而调用方法表中的某个偏移量所指向的方法(Girl 的 speak() 方法的实现)。

接口调用

因为 Java 类是可以同时实现多个接口的,而当用接口引用调用某个方法的时候,情况就有所不同了。Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同样的方法在基类和派生类的方法表的位置就可能不一样了。


清单 3

				
interface IDance{ 
   void dance(); 
 } 

 class Person { 
 public String toString(){ 
   return "I'm a person."; 
	 } 
 public void eat(){} 
 public void speak(){} 
	
 } 

 class Dancer extends Person 
 implements IDance { 
 public String toString(){ 
   return "I'm a dancer."; 
	 } 
 public void dance(){} 
 } 

 class Snake implements IDance{ 
 public String toString(){ 
   return "A snake."; 
	 } 
 public void dance(){ 
 //snake dance 
	 } 
 } 



图 5.Dancer 的方法表(查看大图
图 5.Dancer 的方法表 

可以看到,由于接口的介入,继承自于接口 IDance 的方法 dance()在类 Dancer 和 Snake 的方法表中的位置已经不一样了,显然我们无法通过给出方法表的偏移量来正确调用 Dancer 和 Snake 的这个方法。这也是 Java 中调用接口方法有其专有的调用指令(invokeinterface)的原因。

Java 对于接口方法的调用是采用搜索方法表的方式,对如下的方法调用

invokeinterface #13

JVM 首先查看常量池,确定方法调用的符号引用(名称、返回值等等),然后利用 this 指向的实例得到该实例的方法表,进而搜索方法表来找到合适的方法地址。

因为每次接口调用都要搜索方法表,所以从效率上来说,接口方法的调用总是慢于类方法的调用的。

 

C++ 的实现方式

从上文可以看到,Java 对于多态的实现依赖于方法表,但比较特殊的是,对于接口的支持是非常不同的,每次调用都要搜索方法表。实际上,在 C++ 中,单继承时对于多态的实现非常类似于 Java,但由于支持多重继承,这会碰到和 Java 支持接口动态调用同样的问题,C++ 的解决方案是利用对象的多个方法表指针,不幸的是,这会引入额外的指针调整的复杂性。

单继承

单继承时,C++ 对于多态的实现本质上与 Java 是一样的,也是基于方法表。但 C++ 在编译时就可以确认要调用的方法在方法表中的位置,而没有 JVM 在方法调用时查询常量池的过程。

C++ 编译时,编译器会自动做很多工作,其中之一就是在需要时在对象插入一个变量 vptr 指向类的方法表。如 Person,、Girl 的类定义与上文中 Java 类似,若


清单 4

				
class Person{ 
	 . . . 
 public : 
    Person (){} 
    virtual ~Person (){}; 
    virtual void speak (){}; 
    virtual void eat (){}; 
 }; 

class Girl : public Person{ 
	 . . . 
   public : 
   Girl(){} 
   virtual ~Girl(){}; 
   virtual void speak(){}; 
   virtual void sing(){}; 
 }; 

 

则 Person 与 Girl 实例的内存对象模型为:


图 6.Person 与 Girl 的对象模型
图 6.Person 与 Girl 的对象模型 

如下的调用代码

 Person *p = new Girl(); 
 p->speak(); 
 p->eat(); 

 

经编译器编译后调用代码为:

 p->vptr[1](p); 
 p->vptr[2](p); 

 

这样在运行时,会自然的过渡到对 Girl 的相应函数的调用。

可以看到方法表中没有各自的构造函数,这是因为 C++ 的方法表中仅含有用 virtual 修饰的方法,非 virtual 的方法是静态绑定的,没有必要占用方法表的空间。这与 Java 是不同的,Java 的方法表含有类所支持的所有的方法,可以说,Java 类的所有方法都是”virtual”(动态绑定)的。

多重继承

多重继承下,情况就完全不一样了,因为两个不同的类,其继承自与同一个基类的方法,在各自的方法表中的位置可能不同(和 Java 中的接口情况类似),但 Java 在运行时有 JVM 的支持,C++ 在这里引入了多个指向方法表的指针来解决这个问题,由此带来了调整指针位置的额外复杂性。

若有如下关系的三个类,Engineer 继承自 Person 和 Employee


图 7. 类静态结构关系图
图 7. 类静态结构关系图 

Engineer 实例对象模型为:


图 8.Engineer 对象模型
图 8.Engineer 对象模型 

可以看到 Engineer 实例有两个指向方法表的指针,这是与 Java 大不相同的。

设有如下的代码 ,


清单 5

				
 Engineer *p = new Engineer(); 
 Person * p1 = (Person *)p; 
 Empolyee *p2 = (Employee *)p; 

 

则各指针在运行时分别指向各自的子对象,如下所示:


图 7.Engineer 实例
图 7.Engineer 实例 

C++ 中对象的指针总是指向对象的起始处,如上述代码中,p 是 Engineer 对象的起始地址,而 p1 指向 p 转型成 Person 子对象的指针,可以看到实际上,两者是相等的;但 Employee 子对象的指针 p2 则于 p 和 p1 不同,实际上

 p2 = p + sizeof(Person); 
 p1->eat(); 
 p2->work(); 

 

则编译后生成的调用代码为:

 *(p1->vptr1[i]) (p1) 
 *(p2->vptr2[j]) (p2) 

 

某些情况下,甚至需要将 this 指针调整到整个对象的起始处,如:

 delete p2; 

 

析构函数的 this 指针要被调整到 p 所指向的位置,否则则会出现内存泄漏。设析构函数在方法表中的位置为 0,则编译后为:

 *(p2->vptr2[0]) (p) 

 

对于指针的调整,编译器没有足够的知识在编译时刻完成这个任务。如上例中,对于 p2 所指向的对象,该对象类型可能是 Employee 或任何该类的子类 ( 其它的子类如 Teacher 等 ),编译器无法确切的知道 p2 和整个对象的初始地址的距离 (offset), 这样的调整只能发生在运行时刻。

一般有两种方法来调整指针,如下图:


图 8. 指针调整 - 扩展方法表
图 8. 指针调整 - 扩展方法表 

这种方法将指针所有调整的 offset 存储于方法表的每个条目中,当调用方法表中的方法时,首先利用 offset 的值完成指针调整再做实际的调用。缺点显而易见,增加了方法表的大小,而且并不是每个方法都需要做指针调整。


图 9. 指针调整 -thunk 技术
图 9. 指针调整 -thunk 技术 

这就是所谓的 thunk 技术,方法表的每个条目指向一小段汇编代码,这段代码来保证做指针调整和调用正确的方法,相当于加了一层抽象。

 

多态在 Java 和 C++ 中的实现比较

上文分别对于多态在 Java 和 C++ 中的实现做了比较详细的介绍,下面对这两种语言的多态实现的异同做个小结:

  • 单继承情况下,两者实现在本质上相同,都是使用方法表,通过方法表的偏移量来调用具体的方法。
  • Java 的方法表中包含 Java 类所定义的所有实例方法,而 C++ 的方法表则只包含需要动态绑定的方法 (virtual 修饰的方法 )。这样,在 Java 下所有的实例方法都要通过方法表调用,而 C++ 中的非虚方法则是静态绑定的。
  • 任意 Java 对象只 “指向”一个方法表,而 C++ 在多重继承下则可能指向多个方法表,编译器保证这多个方法表的正确初始化。
  • 多层继承中 C++ 面临的主要问题是 this 指针的调整,设计更精巧更复杂;而 Java 在接口调用时完全采用搜索的方式,实现更直观,但调用效率比实例方法调用要慢许多。

可以看到,两者之间既有相似之处,也有不同的地方。对于单继承的实现本质上是一样的,但也有细微的差别(如方法表);差别最大的是对于多重继承(多重接口)的支持。实际上,由于 C++ 是静态编译型语言,它无法像 Java 那样,在运行时刻动态的“查找”所要调用的方法。

 

参考资料

学习

  • Java 虚拟机规范:Java 虚拟机规范规定了 Java 的具体工作方式,对 Java 语言的各个方面做了全面的阐述。 

  • Java 虚拟机专题:Java 虚拟机(Java virtual machine,JVM)是语言与底层软件和硬件之间的一种转换器。Java 语言的所有实现都必须实现 JVM,从而使 Java 程序可以在有 JVM 的任何系统上运行。 

  • 深入 Java 虚拟机:对 Java 虚拟机的各种可能实现做了独到而清晰的解析。 

  • 深入 C++ 对象模型:深入探讨了 C++ 的对象模型,函数调用机制等,对编译器在幕后所做的工作给出了详尽的解释。 

  • 技巧:用 C 语言实现程序的多态性:使用 C 语言模拟简单的多态特性,该文给出了一个简单的实现。 

  • developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。 

转自:http://www.ibm.com/developerworks/cn/java/j-lo-polymorph/index.html

posted @ 2012-03-27 13:37 longrenle 阅读(11) 评论(0) 编辑

2012年3月19日 #

一直在搞WebRTC,发现其Web API还很不成熟,Chrome的团队也在不停地fix bug,于是下载了WebRTC的源码学习。WebRTC的源码一部分已经merge进了libjingle项目,结构比较复杂。

libjingle里面有一个基类为has_slots,搜索了一下其资料发现是一个很好用的C++库。开源库连接:http://sourceforge.jp/projects/sfnet_sigslot/

下面是转载的别人的资料,一个对sigslot简单清晰的介绍,学习分享一下!

1.    简介

sigslot是一个线程安全、类型安全,用C++实现的sig/slot机制(sig/slot机制就是对象之间发送和接收消息的机制)的开源代码库。是一个非常好用的库,只有一个头文件sigslot.h。

 

2.    Sigslot实例

 

现代的C++项目通常包含大量的C++类和对象,对象之间通过成员函数调用,缺点是当类和对象规模很大时,相互之间必须记住对方提供了哪些接口,以及接口的详细信息,很不方便。

比如:我们有一个switch类和一个light类,而我们现在需要将两者关联起来,即通过switch控制light的状态,我们可能需要添加一个另外的类ToggleSwitch来将两者关联起来:

class Switch

{

public:

     virtual void Clicked() = 0;

};

class Light

{

public:

     void ToggleState();

     void TurnOn();

     void TurnOff();

};

class ToggleSwitch : public Switch

{

public:

     ToggleSwitch(Light& lp){m_lp = lp;}

     virtual void Clicked(){m_lp.ToggleState();}

private:

     Light& m_lp;

};

Light lp1, lp2;

ToggleSwitch tsw1(lp1), tsw2(lp2);

这在功能上完全可以实现,但想象一下如果大量的需要相互交互消息的类,那工作量就不是一般的大了。

使用sig/slot机制来解决上述情况,不需要关心关联类的接口细节,sigslot实现的switch和light上述功能如下:

class Switch

{

public:

     signal0<> Clicked;

};

class Light : public has_slots<>

{

public:

     void ToggleState();

     void TurnOn();

     void TurnOff();

};

Switch sw1, sw2;

Light lp1, lp2;

   

     Sigslot机制实现该功能与第一种方法相比,switch类多了个signal0成员,light类需要从has_slots<>继承,其他没有什么变化,但省去了编写继承类用来实现两者关联的ToggleSwitch。

下面是实现功能的简单代码。

 

#include <iostream>

using namespace std;

 

#include "sigslot.h"

using namespace sigslot; //必须加上sigslot的命名空间

//在用vs调试时还需要将sigslot.h中很多的自定义模板结构类型前加typename

const int TRUE = 1;

const int FALSE = 0;

class Switch

{

public:

    signal0<> Clicked;

//这里的信号是不带参数的,signaln表示带几个参数

};

class Light : public has_slots<>

{

public:

Light(bool state){b_state = state;Displaystate();}

       void ToggleState(){b_state = !b_state;Displaystate();} //作为消息的响应

       void TurnOn(){b_state = TRUE;Displaystate();}

       void TurnOff(){b_state = FALSE;Displaystate();}

       void Displaystate(){cout<<"The state is "<<b_state<<endl;}

private:

       bool b_state;

};

void main()

{

       Switch sw1, sw2,all_on,all_off;

       Light lp1(TRUE), lp2(FALSE);

       sw1.Clicked.connect(&lp1,&Light::ToggleState); //绑定

       sw2.Clicked.connect(&lp2,&Light::ToggleState);

       all_on.Clicked.connect(&lp1,&Light::TurnOn);

       all_on.Clicked.connect(&lp2,&Light::TurnOn);

       all_off.Clicked.connect(&lp1,&Light::TurnOff);

       all_off.Clicked.connect(&lp2,&Light::TurnOff);

 

       sw1.Clicked();

       sw2.Clicked();

       all_on.Clicked();

       all_off.Clicked();

 

       sw1.Clicked.disconnect(&lp1);

       sw2.Clicked.disconnect(&lp2);

       all_on.Clicked.disconnect(&lp1);

       all_on.Clicked.disconnect(&lp2);

       all_off.Clicked.disconnect(&lp1);

all_off.Clicked.disconnect(&lp2);

}

3.    参数类型

sig/slot可以带参数也可以不带,最多可以带8个参数。重新回顾上例,switch类的signal0<> Clicked,称之为sig,即用来发出信号;而继承has_slots<>的类light的成员函数void ToggleState() Turnon() Turnoff(),称之为slot,即信号的处理函数。 sigslot的核心就在这里,就是通过这两个建立对应关系来实现对象间的消息交互。

sig是一个成员变量,它形如

 

signal+n<type1,type2……>

    后面的n表示signal可以接收几个参数,类型任意,最多为8个。这是由库中指定的,当然如果实际开发需要更多的参数,可以修改sigslot库。

slot是一个成员函数,它形如:

void SlotFunction(type1,type2……)

   需要记住:slot的类必须继承has_slots<>;成员函数的返回值必须为void类型,这是这个库的局限性,当然如果实际开发需要返回值,也是可以修改sigslot库来实现。此外还需要注意的是slot的原形需要与sig一致。怎么说呢,就是signal只能与带有与它相同参数个数的slot函数进行绑定,而且signal的参数是直接传递给slot的。

 

4.    Sigslot库用法

发送信号

信号(sig,即sig/slot的sig,下面提到的信号等同于此含义):

signal1<char *, int> ReportError;

比如上面的一个ReportError这个信号,当调用ReportError("Something went wrong", ERR_SOMETHING_WRONG);时候,将自动调用ReportError的emit成员函数发出一个信号。发给谁呢?

 

连接信息号

通过调用sig的connect函数建立sig和slot间的对应关系。Connect函数接收两个参数,一个是消息目的对象的地址(指针),另一个是目的对象的成员函数指针(slot)。为了让整个机制有效运行,目的类必须从has_slots<>继承,并且sig/slot参数类型必须一致。也可以将一个sig连接到多个slot上,这样每次sig发出信号的时候,每个连接的slot都能收到该信号。

 

断开信号连接

通过调用sig的disconnect函数断开sig和slot之间的连接,只有一个参数:目的对象的地址。一般不需要显式调用disconnect函数,在sig类和目的类(包含slot函数的类)析构函数中将自动调用disconnect断开sig和slot的连接。也可使用disconnect_all断开该sig的所有slot。

all_on.Clicked.connect(&lp1,&Light::TurnOn);

all_on.Clicked.connect(&lp2,&Light::TurnOn);//同上

all_on.Clicked.disconnect_all();

5.    Sigslot库范例

在开发一个复杂工程的时候,经常会遇到这样一个问题:整个系统被分成数个模块,每个模块提供有限的功能,由上层调用组成整个系统,为了保证每个模块的独立性,我们经常会尽量限制模块与模块之间的直接联系,比如每个模块只提供有限的API或者COM接口,而内部实现则完全封闭起来。
    但有的时候会出一些设计要求,必须能够使模块之间能够直接通讯,而这两个模块往往处于不同的逻辑层次,之间相差甚远,如何设计它们之间的调用模式使整个工程维持整洁变得非常困难,比如模块直接直接包含对方的头文件会引起编译变得复杂,提供api或者接口会引起版本危机等问题。
    sigslot的出现为我们提供了一种解决问题的思想,它用“信号”的概念实现不同模块之间的传输问题,sigslot本身类似于一条通讯电缆,两端提供发送器和接收器,只要把两个模块用这条电缆连接起来就可以实现接口调用,而sigslot本身只是一个轻量级的作品,整个库只有一个.h文件,所以无论处于何种层次的库,都可以非常方便的包含它。

    举个例子,我们设计一个发送消息的类,这个类负责在某种时刻向外界发出求救信号

// Class that sends the notification.
class Sender
{
public:
    // The signal declaration.
    // The ’2′ in the name indicates the number of parameters. Parameter types
    // are declared in the template parameter list.
    sigslot::signal2<std::string ,int >SignalDanger;
   
    // When anyone calls Panic(), we will send the SignalDanger signal.
    void Panic()
    {
        SignalDanger("Help!",0);
     }
};

另外一个类则负责接收求助信号

// Listening class. It must inherit sigslot.
class Receiver :public sigslot::has_slots<>
{
public:
    // When anyone calls Panic(), Receiver::OnDanger gets the message.
    // Notice that the number and type of parameters match
    // those in Sender::SignalDanger, and that it doesn’t return a value.
    void OnDanger(std::string message,int time)
    {
        printf("I heard something like\"%s\" at %d!\n",message.c_str(),time);
    }
};

现在让我们在主逻辑中把这两个类连接起来

Sender sender;
Receiver receiver;
 
// Receiver registers to get SignalDanger signals.
// When SignalDanger is sent, it is caught by OnDanger().
// Second parameter gives address of the listener function class definition.
// First parameter points to instance of this class to receive notifications.
sender.SignalDanger.connect(&receiver,Receiver::OnDanger);

只要在任何时候调用 sender.Panic()函数,就会把求救信号发送给接收者,而且这两个发送和接收端的模块都可以独立编译,不会出现版本问题。

 

posted @ 2012-03-19 10:55 longrenle 阅读(59) 评论(0) 编辑

2012年3月8日 #

static_cast

 
  用法:static_cast < type-id > ( expression )
 
  该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
 
  ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
 
  进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
 
  进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
 
  ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
 
  ③把空指针转换成目标类型的空指针。
 
  ④把任何类型的表达式转换成void类型。
 
  注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
 
  C++中static_cast和reinterpret_cast的区别
 
  C++primer第五章里写了编译器隐式执行任何类型转换都可由static_cast显示完成;reinterpret_cast通常为操作数的位模式提供较低层的重新解释
 
  1、C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为隐式类型转换使用。比如:
 
  int i;
 
  float f = 166.7f;
 
  i = static_cast<int>(f);
 
  此时结果,i的值为166。
 
  2、C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。比如:
 
  int i;
 
  char *p = "This is a example.";
 
  i = reinterpret_cast<int>(p);
 
  此时结果,i与p的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i,//i 也是指针,整型指针;一个明显的现象是在转换前后没有数位损失。

dynamic_cast

用法
  dynamic_cast < type-id > ( expression )
 
  该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;
 
  如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
 
  dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果downcast不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
 
  dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
 
  在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
 
  在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
 
  class B{
 
  public:
 
  int m_iNum;
 
  virtual void foo();
 
  };
 
  class D:public B{
 
  public:
 
  char *m_szName[100];
 
  };
 
  void func(B *pb){
 
  D *pd1 = static_cast<D *>(pb);
 
  D *pd2 = dynamic_cast<D *>(pb);
 
  }
 
  在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
 
  但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),
 
  而pd2将是一个空指针。

reinterpret_cast

 
  reinterpret_cast是C++里的强制类型转换符。
 
  操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。
 
  例如:int *n= new int ;
 
  double *d=reinterpret_cast<double*> (n);
 
  在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。
 
  因此, 需要谨慎使用 reinterpret_cast.
 
  == ===========================================
 
  == static_cast .vs. reinterpret_cast
 
  == ================================================
 
  reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)
 
  static_cast和reinterpret_cast的区别主要在于多重继承,比如
 
  class A { public: int m_a; };
 
  class B { public: int m_b; };
 
  class C : public A, public B {};
 
  那么对于以下代码:
 
  C c;
 
  printf("%p, %p, %p\r\n", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));
 
  前两个的输出值是相同的,最后一个则会在原基础上偏移4个字节,这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),而reinterpret_cast却不会做这一层转换。
 
  因此, 你需要谨慎使用 reinterpret_cast.

const_cast

 
  用法:const_cast<type_id> (expression)
 
  该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
 
  一、常量指针被转化成非常量指针,并且仍然指向原来的对象;
 
  二、常量引用被转换成非常量引用,并且仍然指向原来的对象;
 
  三、常量对象被转换成非常量对象。
 
  Voiatile和const类试。举如下一例:
 
  class B
 
  {
 
  public:
 
  int m_iNum;
 
  B() {}
 
  };
 
  void foo()
 
  {
 
  const B b1;
 
  //b1.m_iNum = 100; //compile error
 
  B b2 = const_cast<B&>(b1);
 
  /* 也可以做如下转换,体现出转换为指针类型 */
 
  B *b3 = const_cast<B*>(&b1);
 
  /* 或者左侧也可以用引用类型,如果对b3或b4的数据成员做改变,就是对b1的值在做改变 */
 
  B &b4 = const_cast<B&>(b1);
 
  b2. m_iNum = 200; //fine?
 
  }
 
  int main()
 
  {
 
  foo();
 
  return 0;
 
  }
 
  上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;
 
  使用const_cast把它转换成一个非常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
posted @ 2012-03-08 10:55 longrenle 阅读(114) 评论(0) 编辑

2012年3月4日 #

摘要: 最近一直在研究WebRTC,本篇是WebRTC的本地API文档,Web developer了解一下也是有好处的,了解了API的实现原理使用起来才会更顺手。决定翻译是因为这篇字不多,翻一下加深自己的理解,如果对别人有帮助那就更好了。第一次翻译东西拿出来,如果有错误还望指正,英文好一点的还是移步英文原文吧:http://www.webrtc.org/reference/native-apisby longrenleWebRTC Native APIs版本2.0 (libjingle r115)2012年2月WebRTC native APIs文档 是基于 WebRTC spec 文档撰写的. 实现阅读全文
posted @ 2012-03-04 00:48 longrenle 阅读(295) 评论(0) 编辑

2012年2月26日 #

摘要: 在上篇文( 基于html5 WebSocket和WebRTC实现IM和视音频呼叫(一))里我们已经用Jetty-7.5.4.v20111024搭起了一个WebSocket server,现在就可以编写自己的WebSocket Server逻辑完成自己的实现了。一、编写WebSocket服务端逻辑MyWebSocketServlet类继承自Jetty开发包中的org.eclipse.jetty.websocket.WebSocketServlet类,用于实现我们的WebSocket 服务端入口。前期没有编写太多的服务端逻辑,只是实现了接受并记录所有连接client端,并广播所有client端消息阅读全文
posted @ 2012-02-26 16:13 longrenle 阅读(406) 评论(2) 编辑

2012年2月24日 #

摘要: 半年前Google开源了WebRTC项目,并把其加入到chrome dev版本中,实现浏览器之间无插件的视音频多媒体传输。这个新的技术使用了HTML 5和简单的Javascript API,开发者可以很轻松的创建RTC应用,只要浏览器支持,就可在不安装任何扩展和插件的前提下进行实时音频和视频聊天。最近工作中需要对WebRTC做一些调研,于是我计划基于WebSocke和WebRTC实现IM和视音频对话的prototype。在html5 WebSocket出现以前,web版本的IM应用都是基于AJAX轮询的信令传输方式,这种方式的优点是调用rest接口可以实现无状态维护信令传输,server端都有阅读全文
posted @ 2012-02-24 00:04 longrenle 阅读(345) 评论(0) 编辑

2012年2月9日 #

摘要: 作者:阮一峰日期:2007年10月28日今天中午,我突然想搞清楚Unicode和UTF-8之间的关系,于是就开始在网上查资料。结果,这个问题比我想象的复杂,从午饭后一直看到晚上9点,才算初步搞清楚。下面就是我的笔记,主要用来整理自己的思路。但是,我尽量试图写得通俗易懂,希望能对其他朋友有用。毕竟,字符编码是计算机技术的基石,想要熟练使用计算机,就必须懂得一点字符编码的知识。1. ASCII码我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以阅读全文
posted @ 2012-02-09 13:23 longrenle 阅读(38) 评论(0) 编辑

2011年2月18日 #

摘要: JNI是Java Native Interface的缩写。从Java 1.1开始,JNI标准成为java平台的一部分,它允许Java和其他语言进行交互。JNI一开始为C和C++而设计的,但是它并不妨碍你使用其他语 言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比 如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。关于 JNI 的用法很简单,有点像 java 里的 reflect 的工作机制,有兴趣的朋友可以参看Java 本地接口规范http://linux.computers..阅读全文
posted @ 2011-02-18 08:58 longrenle 阅读(1214) 评论(0) 编辑

仅列出标题  下一页