Andy Niu �����ĵ�

Andy Niu

Andy Niu Help  1.0.0.0
深度探索CPP对象模型

变量

 理解实例和静态
 
 字段和方法__实例和静态
 
 抑制多态机制
 
 多态行为的条件
 

详细描述

变量说明

多态行为的条件
1、第一是虚方法,第二表面类型和真实类型不一样,也就是使用引用或者指针。
2、考虑下面的代码,Say是虚方法
    Dog d;
    Animal a = d;
    a.Say();
3、a.Say(); 不具备多态行为。为什么?
    Animal a = d; 会发生对象切割,也就是d中的subobject会赋值给a。
    但是特别注意,其中的vptr字段不会被复制,也就是vptr字段初始化就确定了,不会被修改。
    那么,a的表面类型和真实类型都是Animal,a.Say()调用被编译器转化为(*(a.vptr[1]))(&a);
    取出虚方法表的第2个槽位(注:第一个槽位[0]是type_info,表示类型信息RTTI,即运行时类型信息,typeid和dynamic_cast)
    解引用,vptr指向Animal的虚方法表,然后传递this指针,作为方法第一个参数。
    如果编译器足够聪明,这里可以抑制多态行为,a.Say()转化为a.Animal::Say(); 直接定位到Animal的Say方法。
    如果Animal::Say()是inline方法,效率会更高。
4、考虑多态行为,就是OO(Object Oriented面向对象)。不考虑多态,只是ADT,对数据和操作封装,就是OB(Object Based)
字段和方法__实例和静态
1、考虑字段:
    a、从语义来讲,实例字段对每个对象有意义,针对每个对象,大家各不相同,互不影响。
        静态字段对所有对象和类本身有意义,针对所有对象和类本身,大家都是一样的。
        举例来说,对于Person类,Age是实例字段,MaxAge是静态字段。对于每个Person对象都有一个Age,对于所有的Person对象
        和Person类,最大的年龄是一样的(比如150岁)。
    b、从内存来讲,实例字段是每个对象有独属于自己的一块内存,互不影响。静态字段是所有对象和类本身共享一块内存。
2、考虑方法:
    a、从语义来讲,实例方法可以访问自己的字段和静态的字段,静态的字段是所有对象共享,当然可以访问,
        但是不能访问其他对象的实例字段。特别注意:这里的不能访问其他对象的实例字段是指不能直接访问,
        如果方法的参数列表中包含其他对象,当然可以访问,甚至可以访问同类对象的私有字段,但是不能访问不同类对象的私有字段。
        (特别注意,private有两层含义:当前对象可以访问自己的private成员,也可以访问同类型对象的private成员,
        必须是同类型,Dog对象可以访问其他Dog对象的private成员,但是不能访问Animal对象的private成员)
        静态方法,不依赖具体的对象,即使没有对象,也可以调用静态方法。静态方法只能访问静态字段。
    b、从内存来讲,无论实例方法还是静态方法,在内存中只有一个拷贝。
        相对于普通方法,实例方法在形参表中在第一个位置增加一个形参,为this指针
        相对于普通方法,静态方法可以认为是名称空间内的一个普通方法,类名就是名称空间,静态方法只能访问静态字段,
        也就是只能访问同一个名称空间的字段。静态方法可以通过类名访问,也可以通过对象访问,通过对象访问,在内部没有使用this指针。
3、理解了静态方法,就知道为什么静态方法不能访问实例字段。静态方法不能是const,为什么?
    const方法解决什么问题,对于实例方法,this指针不能修改指向,可以修改this指针的内容,为了限制修改this指针的内容,
    使用const方法,也就是让this指针指向const对象。但是静态方法压根就没有this指针,所以const修饰静态方法没有意义。
4、字段和方法编译之后,也就是数据段和代码段,数据段存在多个内存拷贝(有可能使用COW技术,减少内存拷贝的个数),
    代码段只有一个内存拷贝,而且是只读的,可以加工同一类型的数据。同步控制加锁,表面上是对代码加锁,实际上是对数据加锁。
参见
抑制多态机制
1、我们知道,虚方法的调用,编译器会转化为虚方法表中对应的slot,然后调用。在有些情况下,我们不期望这种多态机制。
2、考虑,下面的情况
    #include <stdio.h>
    class Animal
    {
    public:
        virtual void Say(int a);
    };
    
    #include "Animal.h"
    void Animal::Say(int a)
    {
        printf("Animal::I am Say(int a)");
    }
    
    #include "Animal.h"
    class Dog : public Animal
    {
    public:
        virtual void Say(int a);
    };
    
    #include "Dog.h"
    void Dog::Say(int a)
    {
        Say(a);
        printf("Dog::I am Say(int a)");
    }
    
    测试代码,
    int main(int argc, char* argv[])
    {
        Animal* pa = new Dog();
        pa->Say(1);
        return 0;
    }
    在子类中调用Say方法,由于多态机制,又调用子类的Say方法,变成死循环,堆栈溢出Stack Overflow
    如何解决?
    这个时候需要抑制多态机制,如下:
    void Dog::Say(int a)
    {
        Animal::Say(a);
        printf("Dog::I am Say(int a)");
    }
3、考虑下面的情况,增加一个虚方法Play,在Dog的Play方法中,如下:
    void Dog::Play()
    {
        Say(5);
        printf("Dog::I am Play()");
    }
    
    这里的Say会进行多态机制,调用Dog的Say方法。我们知道,这种情况下,其实不需要多态机制,直接调用Dog的Say方法。如下:
    void Dog::Play()
    {
        Dog::Say(5);
        printf("Dog::I am Play()");
    }
    相对于上一种做法,这种做法减少了运行期对虚方法的决议过程(也就是转化为对应的slot),编译器确定了方法,效率高一些。
参见
理解实例和静态
1、成员分为字段和方法
2、考虑字段,实例字段占用对象内存,每个对象有专属于自己的一块内存,存储字段的值。
    静态字段不占用对象内存,内存中只有一份,类和所有对象共享。
3、考虑方法,和字段不同,方法是代码段。
    无论是实例方法还是静态方法,并不占用对象的内存。在内存中只有一份,而且是只读的。
    方法是相对于类的,实例方法关联this指针,静态方法不关联this指针,只能访问静态字段。
4、考虑方法内的static字段,是针对所有对象的,所有对象都会调用这个方法。
    这个static字段不光是当前对象进来只执行一次,是所有对象进来只执行一次。
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.