C++ 零散知识点速记 -- <深入探索C++对象模型> 读书笔记

错误认知

没有任何构造函数的时候, 编译器总会生成默认构造函数

编译器仅在必要的时候生成默认构造函数
析构函数同理

条件 :

  • 有带有默认构造函数的member
  • 有带有默认构造函数的base class
  • 有virtual function
  • 有virtual inherit

任何对象都带有vptr / 可以对任何对象指针进行 dynamic_cast 操作

只有对象具有 多态 属性的时候 , 才具有 vptr , 才可以对其指针进行 dynamic_cast .

对一个没有多态 属性的指针进行dynamic_cast会导致编译器报错.

    class GrandPa
    {
        public:
            int  a;
            char a1;
            // virtual void func(){}
    };

    class Father : public GrandPa 
    {
        public:
            int b;
            char a2;
    };

    class Son : public Father 
    {
        public:
            int i;
            char a3;
    };
    int main()
    {
        Son s;
        Father f ;
        GrandPa *  p1 = &s;
        GrandPa * p2 = &f;

        printf("The addr of p1 : %p\n" , dynamic_cast<Son*>(p1) ) ;
        printf("The addr of p1 : %p\n" , dynamic_cast<Father*>(p1) ) ;
        printf("The addr of p2 : %p\n" , dynamic_cast<Son*>(p2) ) ;
        return 0;
    }

报错:

c++_test.cpp: In function ‘int main()’:
c++_test.cpp:42:63: error: cannot dynamic_cast ‘p1’ (of type ‘class GrandPa*’) to type ‘class Son*’ (source type is not polymorphic)
c++_test.cpp:43:66: error: cannot dynamic_cast ‘p1’ (of type ‘class GrandPa*’) to type ‘class Father*’ (source type is not polymorphic)
c++_test.cpp:44:63: error: cannot dynamic_cast ‘p2’ (of type ‘class GrandPa*’) to type ‘class Son*’ (source type is not polymorphic)

取消对GrandPa的虚函数的屏蔽 , 结果:

The addr of p1 : 0x7fff8f017ee0
The addr of p1 : 0x7fff8f017ee0
The addr of p2 : (nil)

新知识点

C++ 语言保证”出现在派生类中的基类对象 有其原样完整性”

指向Data Member 的指针含义 : member 在对象中的偏移

例子 :
    class GrandPa
    {
        public:
            int a;
            char a1;
    };

    class Father : public GrandPa 
    {
        public:
            char a2;
    };

    class Son : public Father 
    {
        public:
            char a3;
    };

    #include <iostream>
    #include <stdio.h>
    using std::cout;
    using std::endl;
    int main()
    {
        cout<<"Sizeof GrandPa : "<<sizeof(GrandPa)<<endl;
        cout<<"Sizeof Father : "<<sizeof(Father)<<endl;
        cout<<"Sizeof Son : "<<sizeof(Son)<<endl;

        printf("The offset of a1 : %p\n", &Son::a1 );
        printf("The offset of a2 : %p\n", &Son::a2 );
        printf("The offset of a3 : %p\n", &Son::a3 );
        return 0;
    }
输出结果:

Linux / g++

命令

g++ test.cpp

输出

Sizeof GrandPa : 8
Sizeof Father : 12
Sizeof Son : 12
The offset of a1 : 0x4
The offset of a2 : 0x8
The offset of a3 : 0x9

Window / Visual Studio

Sizeof GrandPa : 8
Sizeof Father : 12
Sizeof Son : 16
The offset of a1 : 0x4
The offset of a2 : 0x8
The offset of a3 : 0xC

分析 :
  1. 由于地址对齐,GranPa的大小是8byte 而不是5byte
  2. a1 的偏移为4

  3. GranPa 类的大小为8byte , 派生类Father的member只能从第8地址(第九位)开始排。

  4. a2 的偏移为8 , 这显然是为了保持GrandPa类的原样完整性实现的
  5. 对于VS Son的大小16byte和a3 的偏移12显然也是保持Father类原样完整性 。
问题:

为什么Linux / GCC 下的 Son的大小也是12 ? 为什么 a3 的偏移是9而不是12

将代码改为:

  • Son类中添加了一个int , 声明先于a3 .

    class GrandPa
    {
        public:
            int  a;
            char a1;
    };

    class Father : public GrandPa 
    {
        public:
            //int b;
            char a2;
    };

    class Son : public Father 
    {
        public:
            int i;
            char a3;
    };

    #include <iostream>
    #include <stdio.h>
    using std::cout;
    using std::endl;
    int main()
    {
        cout<<"Size of GrandPa : "<<sizeof(GrandPa)<<endl;
        cout<<"Size of Father : "<<sizeof(Father)<<endl;
        cout<<"Size of Son : "<<sizeof(Son)<<endl;

        printf("The offset of a : %p\n", &Son::a);
        printf("The offset of a1 : %p\n", &Son::a1);
        printf("The offset of a2 : %p\n", &Son::a2);
        printf("The offset of a3 : %p\n", &Son::a3);
        printf("The offset of i : %p\n", &Son::i);
        return 0;
    }

输出:

Size of GrandPa : 8
Size of Father : 12
Size of Son : 20
The offset of a : (nil)
The offset of a1 : 0x4
The offset of a2 : 0x8
The offset of a3 : 0x10
The offset of i : 0xc

可以看到 g++ 对内存做了适当的优化 , 将a3 先于 i 存放.

  • 在Father 类中添加int
    对上面的代码中的 //int b 打开 , 得到输出:

Size of GrandPa : 8
Size of Father : 16
Size of Son : 24
The offset of a : (nil)
The offset of a1 : 0x4
The offset of a2 : 0xc
The offset of a3 : 0x14
The offset of i : 0x10

这一次没有之前的优化了 . 可以推断 :

g++ 在保证父类的原样完整性的时候, 如果父类也是个派生类, 那么将对父类仅保证 分段 的原样完整性,
也就是说 , 在第一个代码例子中, Son类中Father类的原样完整性是通过保证GrandPa类的原样完整行和Father类中独有的数据char a2 的原样完整性来进行的 . 这样虽然Father类的大小是12 byte, 但是原样完整性仅仅需要9byte !!!

你可能感兴趣的:(C++)