(Boolan) C++面向对象高级编程(五)

对象模型

虚表和虚指针

  • 关于虚表和虚指针以及内存对其等问题,我之前的文章做了非常详细的测试和整理,如果需要查看,可以查看后面的链接: http://www.jianshu.com/p/46acf45ee795
    再次我对主要的知识点做一些梳理
  • 虚指针
    • 类中如果存在虚函数,就会在内存中存在虚指针

    • 在对象的内存空间中,只需要持有虚函数们的一个指针(实际虚函数们是以个虚函数的数组)

    • 由于虚函数指针,实际持有的实际是一个数组的指针,那么无论class设计有多少个虚函数,对于对象的大小来说都只增加一个指针的大小

    • 继承过程实际是继承了父类的函数的调用权,所以,父类有虚函数,子类也一定会有虚函数

    • 示例

      • UML


        类UML图
      • 虚指针和虚表图

虚指针和虚表

由图可以看出,在虚表中放的全都是虚函数的指针。其中void vfunc2()是在class A中定义的,而他的子类都继承自class A,并且没有被覆写,所以,A、B、C中的虚表都同时具有指针指向A::vfunc2(),而其余的虚函数,都是在各自class中定义的,所以各自会指向自己定义的虚函数(B::vfunc1()、C::vfunc1())

子类调用虚函数:

C* pc = new C;//创建C的对象
pc->vfunc2(); //通过c的指针,调用未被覆写的虚函数
上方的调用过程,实际是相当于 (*(pc-> vptr)[n])(pc); 或 (*pc->vptr[n])(pc);
// 也就是说,调用过程会动态从数组(实际就是虚表)中,取出函数的指针,并把pc作为实参传入该函数,以实现调用

动态绑定调用虚函数:

A* pa = new C; //C的对象向上转型
pa->vfunc2(); //实际会调用C中所实现的vfunc2()

//实际过程,((pa->vptr)[n])(pa);或(pa->vptr)(pa),而pa实际指向的为子类地址,所以看似是父类在调用,实则为子类在调用。所以调用时具体还是需要看指针所指向的对象

通过以上的原理,C++可以实现非常强大的可扩展性,无需判断具体是哪个类来调用相应的函数

实现多态(polymophism)条件:调用者是指针,向上转型(父类指针 = 子类指针),调用虚函数
  • 模版方法(Template Method)
模版方法的UML

父类中的OnFileOpen()函数中调用了自己的虚函数Serialise()。

子类对象再调用OnFileOpen()函数执行到Serialize()函数式,会调用子类所实现的Serialize()函数。

原理:在父类的OnFileOpen()函数中调用自己的虚函数Serialise()时,实际是this->Serialize();的过程,由于Serialize();为虚函数, 并且子类对象符合了向上转型父类* x = new 子类,所以,实际相当于(*(this->vptr)[n])(this);的动态绑定过程。此时子类对象.OnFileOpen()子类调用OnFileOpen()时,相当于父类::OnFileOpen(子类对象的指针);,所以此时的OnFileOpen()中的this指针,实际是子类的指针,所以可以调用到子类所实现父类虚函数的实现函数了。

  • 动态绑定
    • 调用函数的是一个指针,使用对象调用仅仅是静态绑定
    • 子类对象向上转型
    • 调用的函数为虚函数

const关键字

| const object (data member不得变动)| non-const object (data member可变动)
----|------|----
const member functions (保证不改变data members) | √ | √
non-const member functions (不保证data members不改变)| × | √

问题,常量对象是不能调用非常量函数的, 那么在实际应用者有什么例子吗?

charT operator[] (size_type pos) const {.....//不需要考虑copy on write}
reference operator[] (size_type pos) {....// 必须考虑copy on write}
//对于class template std::basic_string具有以上的两个成员函数。
//原因:对于字符串来说,无论常量还是非常量,都应该可以通过[index]来获取其中的某一个字母,此时无法确认是否是那种调用,必须有两个函数来由编译器自动区分,以便常量对象调用常量成员函数,非常量对象调用非常量成员函数
  • const关键字的初始化
    const修饰的变量必需 初始化
int i = 41;
const int c1 = i;  //正确,i的值拷贝给了c1
int j = c1;  //正确。ci的值拷贝给了i

拷贝一个对象的,并不会改变它,一旦拷贝完成,心的对象就和原来的对象没什么关系了

  • const对象默认情况下只在文件内有效。
    当多个文件中出现了同名的const变量时,其实等同于不同文件中分别定义了独立的变量。(解决方案是在定义和声明的地方都是用extern关键字修饰,两处则为同一个对象)

new和delete

new和delete的分解动作

  • new:先分配memory,再调用Constructor

TypeName *pt = new TypeName(params...);的编译器处理过程:
1. void *mem = operator new (sizeof(TypeName)); //分配所需的内存,operator new的内部调用了malloc(n)来分配空间
2. pt = static_cast<TypeName *>(mem); //类型强转
3. pt->TypeName::TypeName(params...); //调用class所对应的构造函数

  • delete:先分配deconstructor,再释放内存

delete pt; 的编译器处理过程
1 TypeName::~TypeName(pt); //调用析构函数
2 operator delete(pt); //释放内存,operator delete内部调用了free(pt)

重载operator new 和operator delete

  • 全局的重载
    • 调用者为编译器
    • 范围为全局
void* myAlloc(size_t size){return malloc(size);}
void myFree(void* ptr){return free(ptr); }
.....
//痊愈重载,不能放在任何的namespace中
inline void* operator new (size_t size){
    cout << "global new() \n"; 
    return myAlloc(size);
}
inline void* operator new[](size_t size){
    cout << "operator new[]\n";
    return myAlloc(size);
}
inline void operator delete(void* ptr){
    cout << "global delete()\n";
    myFree(ptr);
}
inline void operator delete[](void* ptr){
    cout << "global delete[]\n";
    myFree(ptr);
}
  • 重载member operator new/ delete
class Foo{
public:
    void* operator new(size_t);
    void operator delete(void* , size_t);//第二参数是可选参数,可以不写
//.......
}
  • 重载后的调用过程
    • Foo* p = new Foo;
     //Foo* p = new Foo;执行过程
    try{
        void* mem = operator new (sizeof(Foo));
        p = static_cast<Foo*> (mem);
        p->Foo:Foo();
      }
    
    • delete p;
//delete p;的执行过程
p->~Foo();
operator delete(p);
  • 重载member operator new[]/delete[]
class Foo{
public:
    void* operator new[](size_t);
    void operator delete[](void* , size_t);//第二参数是可选参数,可以不写
//.......
}
  • 重载后的调用过程
    • Foo* p = new Foo[N];
     //Foo* p = new Foo[N];执行过程
    try{
        void* mem = operator new (sizeof(Foo));
        p = static_cast<Foo*> (mem);
        p->Foo:Foo();//调用N次
      }
    
    • delete [] p;
//delete [] p;的执行过程
p->~Foo();  //调用析构函数N次
operator delete(p);

重载new()和delete()

  • operator new()
    可以重载class member operator new(), 写出多个版本,前提是每一个版本声明都必须有独立的参数列,其中第一参数必须是size_t,(其实就是unsign int),其余参数以new所指定的placement arguments为初值。出现new(....)小括号便是所谓的placement arguments。
    Foo* pf = new(300, 'c')Foo; //设计时参数为三个,size_t, int, char
  • operator delete()
    也可以重载class member operator delete(),写出多个版本。但他们绝对不会被delete调用。只当new锁掉用的ctor抛出异常,才会调用这些重载版本的operator delete()。他们可能这样被调用,主要用来归还未完成创建功能的object所占用的内存。

  • 示例

class Foo{
public:
    Foo(){ cout << "Foo::Foo() " << endl; }
    Foo(int){
       cout << "Foo::Foo(int)" << endl;
       throw Bad();   //class Bad{};
       //故意抛出exception,测试placement operater delete
    }

//1.这个就是一般的operater new()的重载
void* operator new(size_t size){return malloc(size); }

//2.整个就是标准库已经提供的placement operater new()的重载的形式
//所以也模拟Standard placement new,救治传回pointer
void* operator new(size_t size, void* start){return start;}

//3.这个才是placement new
void* operator new (size_t size, long extra){return malloc(size + extra); }

//4.也是一个placement new
void* operator new(size_t size, long extra, char int){return malloc(size + extra); }

//以下是搭配上述placement new的各个所谓placement delete
//当ctor发出异常,这对应的operator (placement) delete就会被调用
//其用途是释放对应的placement new分配的内存
1. 这是个一般的operater delete()
void operator delete(void*, size_t){cout << "operator delete(void*, size_t)" << endl;}

//2. 这是对应void* operator new(size_t, void*)
void operator delete(void*, void*){}

//3.这是对应void* operator new(size_t, long)
void operator delete(void*, long){}

4.这是对应的void* operater new (size_t, long, char)
void operator delete(void*, long, char){}
//如果placement operater deleter如果不和placement operater new一一对应,程序不会报错

private:
    int m_i;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容