前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >本周阅读:深度探索C++对象模型

本周阅读:深度探索C++对象模型

作者头像
程序员小王
发布2019-05-05 16:33:48
7820
发布2019-05-05 16:33:48
举报
文章被收录于专栏:架构说

1 下面代码有什么问题(工作中遇到的)

代码语言:javascript
复制
class Base
{
  virutal hi() { }
};
class Child : public Base
{
public:
    Child():m_c(10)
    {

    }

    int m_c;
};
int main()
{
    Base* ptr=new Child() ;
    cout<<ptr->m_c; //通过基类访问子类,没有啥问题呀?

    return 0;
}

编译出错 ,修复如下

代码语言:javascript
复制
    //method1 
    cout<<"cast= "<<((Child*)ptr)->m_c<<endl;
     //method2
     if( Child*  pc=dynamic_cast<Child*>(ptr))
     {
       cout<<"dynamic_cast="<<pc->m_c<<endl;

     }

2. 原因分析

深度探索C++对象模型 1.3 章节 https://github.com/wangcy6/weekly/blob/master/reading-notes/object-model/1.object-lessons.md

  • 通过引用和指针方式
代码语言:javascript
复制
Child c;// 
Base *pz = &c; // 
Child *pb = &c;
Each addresses the same first byte of the Child object. 
The difference is that the address span of pb
encompasses the entire Bear object, 
while the span of pz encompasses only the ZooAnimal subobject of
Bear.

pz cannot directly access any members other than those present within the ZooAnimal subobject, except
through the virtual mechanism:

pz pb 虽然指向 地址 同一个地址,没有任何差别,但是对地址解析 需要根据定义类型来来判断 Child 和 Base 大小不一样

  • 对象类型 这个更具体
代码语言:javascript
复制
Bear b;
ZooAnimal za = b;
// ZooAnimal::rotate() invoked
za.rotate();
问题1

So, then, why is it that, given the instance of rotate() invoked is the ZooAnimal instance and not that of Bear? Moreover, if memberwise initialization copies the values of one object to another,

问题2 通过用子类来拷贝构造 一个父类,为什么父类的vptr不能访问子类的须函数
代码语言:javascript
复制
ZooAnimal za = b;
why is za's vptr not addressing Bear's virtual table?
回答2
代码语言:javascript
复制
The answer to the second question is that the compiler intercedes in the initialization and assignment of one
class object with another. 

The compiler must ensure that if an object contains one or more vptrs, those vptr
values are not initialized or changed by the source object .

译成中文就是,编译器必须要确保如果一个对象有一个或多个vptr,这些vptr不是由原对象来初始化或改变的。 也就是说:当使用赋值的方式或拷贝构造的方式创建一个对象时,这个对象的vptr与源对象无关。

回答1 The answer to the first question is that za is not (and can never be) a Bear; it is (and can never be anything but) a ZooAnimal. Polymorphism, the potential to be of more than one type, is not physically possible in directly accessed objects

3 多态充分必要条件

  • 经由一组隐含的转化操作(把一个derived class指针转化为一个指向其plublic base type的指针) Through a set of implicit conversions, such as the conversion of a derived class pointer to a pointer of its public base type: shape *ps = new circle();
  • 经由virtual function机制 Through the virtual function mechanism: ps->rotate();

在《深度探索C++对象模型》的4.2节能够找到完美答案,具体摘抄如下:   “表格中的virtual functions地址是如何被建构起来的?在C++中,virtual functions(可经由其class object被调用)可以在编译时期获知。此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌控,不需要执行期的任何介入

  • 经由dynamic_cast和typeid运算符 RTT ( 执行期类型识别) dynamic_cast可以在执行期决定真正的类型( 第7章节 ) https://github.com/wangcy6/weekly/blob/master/reading-notes/object-model/7.object-model-cusp.md

4 历史文章修正

1 C++对象模型-构造函数语义学

  • 补充内容

错误理解1 :如果类没有定义任何构造函数,编译器一定会自动生成默认的构造函数

注意:这种说法是错误的(编译器太懒了)

正确的说法: 惟有默认构造函数”被需要“的时候编译器才会合成默认构造函数 默认构造函数是指有用的默认构造函数,其英文名字叫nontrivial default constructor。 对于以下四种情况,编译器会自动生成默认构造函数

  • 情况1 如果一个类没有任何构造函数,但是含有一个类类型的成员变量, 该成员对象有nontrivial default constructor,此时编译器会为该类合成一个默认的构造函数;(普通成员变量就不会了)
  • 情况2 如果一个类没有任何构造函数,但是该类继承自含有默认构造函数的基类,该基类有nontrivial default constructor,此时编译器会为该类合成一个默认的构造函数;(普通基类就不会了)
  • 情况3 带有虚函数的类(添加额外信息)
  • 情况4 带有虚基类的类

有基类的构造函数,有成员的构造函数,还有自己构造函数,哪有优先执行(混乱)? 派生类构造函数执行顺序:

  • 按照继承顺序 调用默认构造函数
  • 按照成员变量声明顺序,如果初四化成员列表 指定构造函数,就用指定的。不如就是默认的。

错误理解2 如果你自己没声明,编译器就会为它声明一个copy构造函数、一个copy assignment操作符和一个析构函数

正确的说法: 对于默认构造函数与复制构造函数,都需要类满足一定的条件时编译器才会帮你合成。那么需要满足些什么条件呢?这条件就是:类不展现bitwise copy 语意的时候 那么在什么情况下一个类才会不展现出Bitwise copy 语意呢? 一个类对于默认的拷贝赋值操作, 在以下情况不会表现出bitwise拷贝语意:

代码语言:javascript
复制
* 当类内带一个成员对象, 而其类有一个拷贝赋值操作时
* 当一个类的基类有一个拷贝赋值操作时
* 当一个类声明了任何虚函数(我们一定不能拷贝右端类对象的vptr地址, 因
  为它可能是一个继承类对象)
* 当类继承自一个虚基类(不论此基类有没有拷贝操作)时

https://github.com/wangcy6/weekly/blob/master/reading-notes/object-model/5.copy-construction-semantics.md

[2 vptr初始化语义 The Semantics of the vptr Initialization 第5.2章节]

  • 补充内容

错误理解3 vptr是是在构造函数内初始化的,

正确理解: 在父类构造函数之后,子类构造函数之前(因为子类构造函数可能执virtual函数) After invocation of the base class constructors but before execution of user-provided code or the expansion of members initialized within the member initialization list

The Semantics of the vptr Initialization

The Semantics of the vptr Initialization

构造函数的执行算法通常如下:

  • 在派生类构造函数中, 所有虚基类以及上一层基类的构造函数会被调用
  • 上述完成之后, 对象的vptr会被初始化, 指向相关的虚表
  • 如果有成员初始化列表的话, 将在构造函数体内扩展开来; 这必须在vptr被 设定之后才进行, 以免有一个虚成员函数被调用
  • 最后执行程序员所提供的代码

编程规范 不要在构造函数中调用虚函数

3 C++编译期多态与运行期多态

  • 补充内容

程序员的自我修养6.4.2节,关于ELF各个section的解释见 在gcc编译器的实现中虚函数表vtable存放在可执行文件的只读数据段.rodata中

虚表属于一个类,里面存储自己的不同对象

5 总结:构造函数的执行算法

构造函数的执行算法

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 下面代码有什么问题(工作中遇到的)
  • 2. 原因分析
    • 问题1
      • 问题2 通过用子类来拷贝构造 一个父类,为什么父类的vptr不能访问子类的须函数
        • 回答2
        • 3 多态充分必要条件
        • 4 历史文章修正
        • 错误理解1 :如果类没有定义任何构造函数,编译器一定会自动生成默认的构造函数
        • 错误理解2 如果你自己没声明,编译器就会为它声明一个copy构造函数、一个copy assignment操作符和一个析构函数
        • 错误理解3 vptr是是在构造函数内初始化的,
        • 5 总结:构造函数的执行算法
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档