nginx开发:c/c++预备知识——类型转换、成员指针、奇异递归模板模式

  介绍c/c++部分知识,整数类型、类型转换、成员指针、奇异递归模板模式。

  看c++代码,尤其是一些库源代码,指不定哪儿就冒出一个语法,让你疑惑迷惘惊叹,竟然还能这样玩。近来有种感觉,c++是个大坑,真是博大精深,简直是无所不包,各种编程模式,各种奇技淫巧,真是技巧细节太多。对于不常用的细节,珍爱生命,珍惜时间,不要沉陷于其中。

一、整数类型

nginx中整数类型

size_t,u_char,off_t;

size_t是sizeof操作符的结果类型,相当于unsigned long.在64位系统中是8个字节。

u_char表示一个字节,是系统标准;在linux中<sys/type.h>里 typedef unsigned char u_char;

自定义整数类型(core/nginx_config.h)

// nginx内部标准整数定义
// ngx_rbtree_key_t = ngx_uint_t (ngx_rbtree.h)
// ngx_rbtree_key_int_t = ngx_int_t(ngx_rbtree.h)
// ngx_msec_t = ngx_rbtree_key_t = ngx_uint_t (ngx_time.h)
// ngx_msec_int_t = ngx_rbtree_key_int_t ngx_int_t(ngx_time.h)
typedef intptr_t        ngx_int_t;      //有符号整数
typedef uintptr_t       ngx_uint_t;     //无符号整数
typedef intptr_t        ngx_flag_t;     //相当于bool,标志量用

inptr_t和uintptr_t是c/c++标准里的两个特殊整数类型,是大小足够容纳指针的整数类型。

nginx 自定义整型主要是保证跨平台的兼容性。 

二、类型转换

  const_cast 通常用来将对象的常量特性去除

  dynamic_cast 主要用来执行“安全向下转型”(safe downcasting),即将基类指针类型转为派生类指针类型。

  reinterpret_cast 执行低级转换 

这些运算符用于删除旧式 C 语言转换中的一些多义性和危险继承。

在万不得已时使用 const_castreinterpret_cast,因为这些运算符与旧的样式转换带来的危险相同。 但是,若要完全替换旧的样式转换,仍必须使用它们。

 static_cast 运算符

  static_cast 运算符在标准 C++ 中,不进行运行时类型检查来帮助确保转换的安全,static_cast 运算符可用于将指向基类的指针转换为指向派生类的指针等操作, 此类转换并非始终安全。

  通常使用 static_cast 转换数值数据类型,例如将枚举型转换为整型或将整型转换为浮点型,而且你能确定参与转换的数据类型。 static_cast 转换安全性不如 dynamic_cast 转换,因为 static_cast 不执行运行时类型检查,而 dynamic_cast 执行该检查。  尽管 dynamic_cast 转换更加安全,但是 dynamic_cast 只适用于指针或引用,而且运行时类型检查也是一项开销。

测试代码:

 1 class Window
 2 {
 3 public:
 4     Window():data(0){cout<<"-----------base crot---------"<<endl;}
 5     virtual void onResize()
 6     {
 7         cout<<"base this=\t"<<this<<"\t"<<"befor data = "<<data<<endl;
 8         data+=10;
 9         cout<<"base &data=\t"<<&data<<endl;
10         cout<<"base this=\t"<<this<<"\t"<<"after data = "<<data<<endl;
11         cout<<"------------------------------------------------------"<<endl;
12     }
13     int data;
14 
15 };
16 class SpecialWindow:public Window
17 {
18 public:
19     virtual void onResize()
20     {
21         cout<<"derive this=\t"<<this<<"\t"<<"before data = "<<data<<endl;
22         cout<<"derive &data=\t"<<&data<<endl;
23         //cout<<"&static_cast<Window>(*this) = "<<&static_cast<Window>(*this)<<endl;
24         //error: taking address of temporary [-fpermissive]|
25 
26         Window* p = &static_cast<Window>(*this); //warning: taking address of temporary [-fpermissive]|
27         cout<<" &p = "<<&p<<"\tp = "<<p<<endl;
28         cout<<"&static_cast<Window>(*this) = "<<&static_cast<Window>(*this)<<endl;
29         cout<<"&static_cast<Window>(*this) = "<<&static_cast<Window>(*this)<<endl;
30 
31         static_cast<Window>(*this).onResize();
32         Window::onResize();
33         data+=100;
34         cout<<"derive this=\t"<<this<<"\t"<<"after data = "<<data<<endl;
35         cout<<"------------------------------------------------------"<<endl;
36     }
37 };
38 
39 void testWindow()
40 {
41     cout<<"sizeof(SpecialWindow) = "<<sizeof(SpecialWindow)<<endl;
42     cout<<"*************************test1*************************"<<endl;
43     SpecialWindow derive;
44     derive.data = 1;
45     cout<<"&derive="<<&derive<<"\t"<<"data = "<<derive.data<<endl;
46     Window* pbase = &derive;
47     cout<<"pbase=  "<<pbase<<"\t"<<"data = "<<pbase->data<<endl;
48     Window* pbase2 = static_cast<Window*>(&derive);
49     cout<<"pbase2=  "<<pbase2<<"\t"<<"data = "<<pbase2->data<<endl;
50     cout<<"*************************test2*************************"<<endl;
51     derive.data = 5;
52     cout<<"begin data = "<<derive.data<<endl;
53     derive.onResize();
54     cout<<"data = "<<derive.data;
55 }
View Code

结果

image

  在当前对象之base class成分的副本上调用Window:: onResize(),然后在当前对象上执行SpecialWindow专属动作,只是修改了临时对象,当前内容没有改动。

reinterpret_cast运算符

  reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。换句话说只是要求编译器按照新的类型对bit位进行解释。

IBM的C++指南里指出应该在什么地方用来作为转换运算符:

  • 从指针类型到一个足够大的整数类型
  • 从整数类型或者枚举类型到指针类型
  • 从一个指向函数的指针到另一个不同类型的指向函数的指针
  • 从一个指向对象的指针到另一个不同类型的指向对象的指针
  • 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
  • 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

MSDN:reinterpret_cast

  允许将任何指针转换为任何其他指针类型,  也允许将任何整数类型转换为任何指针类型以及反向转换。

滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。

  • reinterpret_cast 运算符可用于 char*int*One_class*Unrelated_class* 之类的转换,这本身并不安全。
  • reinterpret_cast 的结果不能安全地用于除强制转换回其原始类型以外的任何用途。 在最好的情况下,其他用途也是不可移植的。
  • reinterpret_cast 运算符不能丢掉 constvolatile__unaligned 特性。 有关移除这些特性的详细信息,请参阅 const_cast Operator
  • reinterpret_cast 运算符将 null 指针值转换为目标类型的 null 指针值。
  • reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。

reinterpret_cast 允许将指针视为整数类型。 结果随后将按位移位并与自身进行“异或”运算以生成唯一的索引(具有唯一性的概率非常高)。 该索引随后被标准 C 样式强制转换截断为函数的返回类型。

三、成员指针

指向成员变量的指针并非指针

  C++中指向成员变量的指针其实是一个相对于类对象的偏移量。《C++必知必会》的条款15讲述了这一说法:“与常规指针不同,一个指向成员变量的指针并不指向一个内存位置。它指向的是一个类的特定成员,而不是指向一个特定对象里的特定成员。通常最清晰的做法是将指向数据成员的指针看作为一个偏移量。......。这个偏移量告诉你,一个特定成员的位置距离对象的起点有多少个字节。”

  1. 类对象访问其成员时,是根据该成员在类中的偏移量来访问的。
  2. 类成员指针,可以理解为指向类数据成员的一个偏移量,而非地址。

  一是对象调用成员函数时会将调用对象与函数绑定;二是对象访问成员是根据该成员距离对象的偏移量来访问的,而不是根据成员名来访问,所谓偏移量,就是告诉你一个特定的成员位置距离对象的起点有多少个字节。

测试代码:

 1 class DemoClass
 2 {
 3 public:
 4     DemoClass() : m_a(100),m_b(10) { }
 5 
 6 public:
 7     int m_a;
 8     int m_b;
 9 };
10 
11 
12 int DemoClass::*ipm = 0; // ipm是一个指针,指向类DemoClass的一个int成员,该处初始化为0
13 int DemoClass::*ipmb = 0; // ipm是一个指针,指向类DemoClass的一个int成员,该处初始化为0
14 
15 void printAddress()
16 {
17     DemoClass aC;
18 
19     cout<<"&(aC)\t=\t" << &(aC) << endl;
20     cout<<"&(aC.m_a) =\t" << &(aC.m_a)<<"\t&(aC.m_b) =\t" << &(aC.m_b) << endl;
21     cout<<"ipm\t=\t" << ipm << endl;
22 
23     // ipm = &(aC.m_a); // 编译错误: cannot convert 'int*' to 'int DemoClass::*' in assignment
24     // ipm = reinterpret_cast<int DemoClass::*>(&(aC.m_a));
25     // error: invalid cast from type 'int*' to type 'int DemoClass::*'|
26     cout<<"******************************************************\n";
27     ipm = &DemoClass::m_a; // ok
28     cout <<"ipm \t=\t"<< ipm << endl; // 1 opps! 注意哦!这里输出1,即offset == 1
29     ipmb = &DemoClass::m_b; // ok
30     cout <<"ipmb \t=\t"<< ipmb << endl;// 1 opps! 注意哦!这里输出为啥也是1
31     printf("ipmb \t=%p\n",ipmb);
32 
33     cout<<"DemoClass->m_a: "<<&((DemoClass*)0)->m_a<<endl;  //0
34     cout<<"DemoClass->m_b: "<<&((DemoClass*)0)->m_b<<endl;  //4
35 
36     printf("%p\n",&DemoClass::m_a);
37     printf("%p\n",&DemoClass::m_b);
38     cout<<"******************************************************\n\n";
39     cout <<"aC.m_a\t=\t"<< aC.m_a << endl;  // 100
40     aC.*ipm = 99; //
41     cout <<"aC.m_a\t=\t"<< aC.m_a << endl;  // 99
42 
43     DemoClass *pC = &aC;
44     cout <<"pC->m_a\t=\t"<< pC->m_a << endl;  // 99
45     pC->*ipm = 1001; //  等价于 aC.*ipm = 99;
46     cout << aC.m_a << " | " << pC->m_a << " | " << pC->*ipm << endl;  // 1001 | 1001 | 1001
47 }
View Code

  image

  ipmb为啥也是1呢,应该是ostream对象没有重载类成员指针的参数,故不能直接输出类成员指针的类型,而我们知道指针类型与bool类型的转换属于标准转换的(常常用来测试指针合法性是否为空),而ostream对象可以输出bool类型,故编译器将成员指针类型转换成了bool类型,从而输出,既然这样为什么全是输出1呢?说明地址全是合法的,即偏移量全是大于0,不对呀,第一个类成员的偏移量不是0么,这里真心不明白,不过《C++必知必会》中有这样一句话:大多数编译器都将成员指针实现为一个整数,包含被指向成员的偏移量,另外加上1(加1是为了让值0可以表示一个空的数据成员指针)。这大概就是全输出1的原因了吧。

  测试代码

class DemoClass
{
public:
    DemoClass() : m_a(100),m_b(10) { }
    // virtual // 是否是虚函数,影响测试pb->vf();
     void vf()
    {
        cout<<"m_a = "<<m_a<<"  "<<"m_b = "<<m_b<<endl;
        printf("0x m_a: %x;  m_b: %x\n",m_a,m_b);
    }
    void fa()
    {
        cout<<"m_a = "<<m_a<<endl;
    }
      void fb()
    {
        cout<<"m_b = "<<m_b<<endl;
    }

public:
    int m_a;
    int m_b;
};

class DeriveClass
{
public:
    DeriveClass():m_a(5),m_c(7){}
    void vf()
    {
        cout<<"m_a = "<< m_a<<"  "<<"m_c = "<< m_c<<endl;
        printf("0x m_a: %x;  m_c: %x\n",m_a,m_c);
    }

public:
    int m_a;
    int m_c;
};
void  (DemoClass::*f)() = 0; // 该处初始化为0
void  (DemoClass::*vf)() = 0; // 该处初始化为0
View Code

image

  试想pb->vf(),输出结果是什么,虚函数有影响吗?

  pb->vf()是DeriveClass的函数为什么却输出DemoClass的内容。内存中实例化了一个DemoClass类对象,然后将该地址强制转换成一个DeriveClass类地址,即将该对象的地址内容强制看成一个DeriveClass类对象。pb为DeriveClass类的指针,理所当然调用的是DeriveClass类中的vf()函数,当调用vf()函数时,调用对象与该函数进行绑定,即vf()函数中隐含的形参this指针初始化为调用对象(DemoClass类对象)的地址。然后vf()函数打印值m_c。这里要注意,对象在访问类成员时,编译器并没有存储该对象各个成员的实际地址,而是存储了其相对于当前对象首地址的偏移量,由于DeriveClass类有两个成员m_a偏移量为0,m_c偏移量为4,在编译阶段,编译器就记录了m_c对于DeriveClass类对象的偏移量为4,故访问m_c时,便是访问当前对象地址this+偏移量4,注意,this在这里绑定的是DemoClass类对象的首地址。在DemoClass类中,若有虚函数,偏移量为0的成员是m_a,偏移量为4的成员是m_b,故打印出虚函数vf和m_a的值;若无虚函数偏移量为0的成员是虚函数,偏移量为4的成员是m_b,故打印出m_a和m_b的值(100,10)。

关于成员指针

  成员指针只是记录一个成员的偏移量,而非地址,因为类中没有地址,选择一个类的成员只是意味着在类中偏移,只有把这个偏移和具体对象的首地址结合,才能得到实际地址。

  成员指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象的特定成员,最直接的理解是将其理解为一个偏移量。这个偏移量适用于某一类A的任何对象,换言之,如果一个A类对象的成员a距离起点的偏移量是4,那么任何其他A类对象中,a的偏移都是4字节。

 四、奇异递归模板模式

  将派生类本身作为模板参数传递给基类。

  具体可参考博客:奇异递归模板模式( Curiously Recurring Template Pattern,CRTP) http://blog.csdn.net/daniel_ustc/article/details/73699378

 

参考内容:

nginx完全开发指南—使用c、c++和openresty ,罗剑锋

强制转换运算符 https://msdn.microsoft.com/zh-cn/library/5f6c9f8h.aspx

IBM Knowledge Center - https://www.ibm.com/support/knowledgecenter

类成员指针 & 成员在类中的偏移量 http://www.ahathinking.com/archives/98.html

C++之类成员指针 - CSDN博客 http://blog.csdn.net/e5max/article/details/11678259

posted on 2018-01-09 21:59  flysong  阅读(418)  评论(0编辑  收藏  举报

导航