2011年四月

C++沉思录读书笔记:句柄类的实现

对于某些类来说,能够避免复制是很有好处的,比如某些对象会很大,复制起来消耗太大,这时就可以通过 句柄(Handler)的类来实现,它允许在保持类的多态的行为的同时,还可以避免不必要的复制。

句柄类的实现方法

为了避免不必要的对象复制,也就是说允许多个句柄绑定到单个对象上,否则就很难把句柄作为参数传递函数,因为那要求复制句柄而不复制对象。因此我们必须了解有多少个句柄绑定在同一个对象上,只有这样才能确定应当在何时删除对象,通常使用“引用计数(use count)”来达到这个目的。

引用计数应该放到何处呢?

1.不能作为句柄的一部分,因为这样的话,每个句柄都必须知道跟它一起被绑定到同一个对象的其他句柄的位置,唯有如此才能更新其他句柄的引用计数数据;

2.作为对象的一部分,这样就要求我们重写已经存在的对象类;

3.定义一个新的类,来保存对象和引用计数(可以考虑);

4.在句柄类中保存引用计数的指针地址,绑定到同一个对象的句柄指向同一个引用计数值;

5.单独抽象出引用计数类,为句柄类提供接口(终极方案);

使用句柄类时还必须关注一个问题,当对句柄所绑定的对象进行修改时,句柄的语义就分为值语义和指针语义:

值语义:修改句柄绑定的对象,但是不影响其他句柄 (使用写时复制copy-on-write)

指针语义:修改句柄绑定的对象,其他绑定到该对象的句柄也同样修改 (指针的作用)

具体实现请参考C++沉思录



C++沉思录读书笔记:类设计者的核查表

每一个使用C++设计类时,都有必要将下面的14条准则烂熟于心:

1. 你的类需要一个构造函数吗?

2. 你的数据成员是私有的吗?

通常公有的数据成员不是什么好事

3. 你的类需要一个无参的构造函数吗?

如果一个类已经有了构造函数,编译就不会提供默认的无参构造函数,如果需要必须显示地写一个无参的构造函数

4. 是不是每个构造函数初始化所有的数据成员?

构造函数的用途就是用一种明确定义的状态来设置对象,而对象的状态由对象的数据成员进行反应,因此构造函数都要负责为所有的数据成员设置经过明确定义的值。当然也会有写例外,类会有一些数据成员,他们只在对象存在一定时间后才会有意义。

5. 类需要析构函数吗?

应该确认类是否分配了资源,这些资源会不会由成员函数自动释放,特别那些构造函数中使用了new表达式的,通常需要在析构函数中使用delete表达式来显式地释放资源

6.类需要一个虚析构函数吗?

任何虚函数只有在继承的情况下才会有用!

7. 类需要复制构造函数吗?

通常有析构函数的类也需要一个复制构造函数!

8. 类需要一个赋值操作符吗?

9. 赋值操作符能正确地将对象赋给对象本身吗?

String& String::operator =(const String& s) 
{
     if (&s != this) {
           delete[] this;
           data = new char[strlen(s.data) + 1];
           strcpy(data, s.data);
    }
    return *this;
}

10. 类需要定义关系操作符吗?
11. 删除数组时请记住用delete[]
12. 记得在复制构造函数和赋值操作符的参数类中加上const
13. 如果函数有引用参数,它们应该是const引用吗?
14. 记得适当地声明成员函数为const了吗?



redis中常用数据结构介绍

1. 常用数据结构

dict

在redis中最基本的三个数据结构是dict 、adlist和sds,其中dict是redis中最重要的数据结构了,其key-value的映射关系就是通过dict来实现的,dict的内部实现是hash table,这个哈希表的大小是动态增加或减少的,主要是依据哈希表中的元素个数;同时哈希表适用链接法来解决哈希冲突的,具体实现在dict.h和dict.c文件中;

adlist

adlist(a generic doubly linked list)双向链表,这个数据结构在redis中用的也比较多,包括像当前保存的客户端连接或者是value对应是list的实现等,都是用的adlist,这个应该来说比较简单,具体实现在adlist.h和adlist.c;

sds

还有一个比较基本的数据结构就是sds(dynamic string),对字符串处理的简单封装,具体细节实现在sds.h和sds.c中,有一篇文章是介绍sds的实现的,点击这里

更多 >



470px-Skip_list.svg

redis中sorted set的实现原理

从redis 1.1版本,redis开始支持sorted set(有序集合),今天在看redis源码时,具体看了它的实现;

关于ZSET的具体用法:http://redis.io/commands#sorted_set

ZSET的实现用到了两个数据结构:hash table 和 skip list(跳跃表),其中hash table是具体使用redis中的dict来实现的,主要是为了保证查询效率为O(1) ,而skip list(跳跃表)主要是保证元素有序并能够保证INSERT和REMOVE操作是O(logn)的复杂度。

关于skip list这里简单介绍下:skip list是链表的一种特殊形式,对链表的一种优化;保证INSERT和REMOVE操作是O(logn)的负载读,而通用链表的复杂度为O(n);

关于skip list的详细介绍请参考下面这篇文章:

http://blog.csdn.net/caoeryingzi/archive/2010/11/18/6018070.aspx

sorted set的用途:

可以用作实时排名,例如微博用户的排名

还有TOPN问题等

http://wangyuanzju.blog.163.com/blog/static/1302920099311165490/



redis源码分析资料

最近闲来无事,看看redis源码,看看redis为何如此高效~
下面是redis代码分析的资料,记录下:

比较全面但不太详细的分析:Redis: under the hood

简单的读和写的完整处理过程:More Redis internals: Tracing a GET & SET (同时也是一个挺好的GDB调试研究源码的实例教程)

关于虚拟内存:Virtual memory

其它资料:http://redis.io/documentation

那就先从redis最原始的1.0版本开始看吧,这里需要说明下,一般学习开源软件代码,最初的版本代码量比较少,看起来不是很费劲,而且基本上能够体现软件的架构信息。

有关本站redis的内容请点击这里


cgiarch

C++ Web编程

C++ Web编程

译者 yaronli (http://www.yaronspace.cn/blog)

原文地址:http://www.tutorialspoint.com/cplusplus/cpp_web_programming.htm

什么是CGI

CGI( Common Gateway Interface ),公共网关接口是一组标准,该标准定义了在 web server和普通脚本间是如何交换信息的。

Web浏览 (Web browsing)

为了更好的理解CGI的概念,我们看看当用户为了访问某个页面或者url点击链接时所发生的事情:

  • 你的浏览器联系到HTTP 服务器,然后请求对应的URL,即对应的文件;
  • Web服务器会解析这个URL,然后查找对应文件名。如果找到这个文件,Web服务器就会返回文件的内容给浏览器,否则 的话会返回一条错误信息;
  • Web 浏览器从Web服务器获得响应信息,然后解析返回的文件内容或者是错误信息;

但是,可以这样设置HTTP服务器,当一个文件被请求时,不是将文件内容返回,而是将它作为程序来执行,并将程序产生的输出返回给你的浏览器来显示。

公共网关接口(CGI)就是这样一种协议,它使应用程序(叫做CGI程序或者是CGI脚本)能够与Web服务器交互。CGI程序可以使用Python、perl、Shell、C或者C++来编写。

CGI的架构图

下图展现了CGI的简单架构:

Web服务器的配置

在你处理CGI程序之前,请确定你的Web服务器支持CGI程序并且它配置为支持能够处理CGI程序。所有的被HTTP服务器执行CGI程序都会预先配置在规定的目录,这个目录传统上被命名为/var/www/cgi-bin/,默认情况下cgi程序的扩展名为.cgi,尽管它们是C++可执行文件。

更多 >



C中宏使用小贴士与小技巧[原创]

C中宏使用小贴士与小技巧

译者 yaronli(http://www.yaronspace.cn/blog)

英文地址:http://www.mikeash.com/pyblog/friday-qa-2010-12-31-c-macro-tips-and-tricks.html

预处理 VS 编译

为了理解C中的宏,首先需要理解一个C程序是怎么编译的。特别的,你必须知道在预处理阶段和编译阶段发生的事情的不同之处。

就像名字所说的,预处理首先执行。它会做一些简单的文本操作,比如:

  • 去除注释
  • 处理#include 指令,用include文件内容对它们进行替换
  • 判断#if 和 #ifdef指令
  • 评估#define指令
  • 根据已经定义#define指令,扩展哪些在剩余代码中找到的宏

显然,最后两条与今天讨论的内容最相关。

可以看到,预处理器对它所处理的文本内容并不关心。对于这点也有例外,比如,它知道这是个字符串,并不会扩展里面的的宏:

#define SOMETHING hello
 
char *str = "SOMETHING, world!" // nope

同时它也可以计算括号的数目,所以它知道这个逗号不会传递给宏两个参数:

#define ONEARG(x) NSLog x
 
ONEARG((@"hello, %@", @"world"));

但是通常来说,预处理器对它所处理的内容并不了解。例如,你不能使用#if来判断一个类型是否已经定义:

// makes no sense
 
#ifndef MyInteger
 
typedef int MyInteger
 
#endif

即使MyInteger类型已经定义,#ifndef也会返回true,类型定义发现在编译阶段,此时还未发生。

同样的,对于#define定义的内容也不需要是语法正确的。下面的定义是完全合法的,尽管这是定义宏的很不好的方式:

#define STARTLOG NSLog(
 
#define ENDLOG , "testing");
 
STARTLOG "just %" ENDLOG

预处理只是盲目地用它们定义的内容来替换STARTLOG和ENDLOG。到时编译器会使此段代码有意义,而且它确实是有意义的,完全可以编译成为有效地代码。 更多 >