周二面了腾讯,之前只投了TST内推,貌似就是TST面试了

其中有一个问题,“如何产生一个不能被继承的类”,这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一个单例模式,但面试官说,单例模式作为此题的解答不够灵活,后来面试官提示说,可以用友元+虚继承,可以完美实现这样一个类

当然那时我还不太明白,友元与虚继承我都极少接触过,只是知道有这些东西,回头搜了一下“不能被继承的类”的做法,具体如下:

1,声明一个类,CNoHeritance,构造函数为private,并声明友元类CParent;
2,让CParent虚继承CNoHeritance
这样CParent就成为一个可以被正常实例化,但又不能被继承的类

吴总当时评价说,“呵呵,虚继承,感觉完全是黑科技啊”

这个黑科技真是戳中我笑点,但想到C++经常有些奇妙的东西,现在想总结一下

1,C++构造函数的黑科技

对于阅读过进阶C++书籍的都该知道,编译器会在“需要”的时候,那么什么是需要的时候呢?四种情况:

  • 1,“带有Default Constructor”的Member Class Object
  • 2,“带有Default Constructor”的Base Class
  • 3,“带有至少一个Virtual Function”的Class
  • 4,“带有一个Virtual Base Class”的Class

自动合成的构造函数往往都是public,在派生类中,它的构造函数是可以被使用的,即派生类不会因此受到限制。

那么,如何能使派生类不能使用基类的函数或成员呢?

  • private:只能由:1,该类中的函数;2,其友元函数访问
  • protected:可以被:1,该类中的函数;2,其友元函数;3,派生类(子类)的函数访问
  • public:可以被:1,该类中的函数;2,其友元函数;3,子类的函数;4,该类的对象访问

如果一个类的构造函数声明为private,则其派生类甚至该类的对象都不能访问,意味着两点:

  • 1,该类不能被继承
  • 2,该类不能由系统实例化,即它实例化的对象不会在栈内存上

那么怎么使用该类呢?一般而言,会通过该类的函数来创建

  1. class A
  2. {
  3. private:
  4. A(){}
  5. public:
  6. A& createA()
  7. {
  8. A* p=new A();
  9. return *p;
  10. }
  11. };

然而,这样又引申一个问题:类没有实例化,如何能使用其成员函数呢?

答案是将该成员函数声明为static,这样不需要实例化即可访问,即将上述改为:

  1. class A
  2. {
  3. private:
  4. A(){}
  5. public:
  6. static A& createA()
  7. {
  8. A* p=new A();
  9. return *p;
  10. }
  11. };
  12. A Object=A::createA();

很明显,上面的实例化过程很不方便,简直是艰辛呀,单例模式的其中一种实现就是如此,在此先不讲。这样实现的类,不能被继承,但自己也不好过

so,如果用友元来实现,是怎么实现的呢?

声明一个类,及其友元

  1. class A
  2. {
  3. private:
  4. A(){}
  5. friend class B;
  6. };

那么B是可以调用A的private的构造函数的,那么让B虚继承A会发生什么事呢?

由《深度探索C++对象模型》看到,B内存中将有一份A类的实体,调用A的构造函数构造的,这对于友元类B是可行的

  1. class A
  2. {
  3. private:
  4. A(){}
  5. friend class B;
  6. };
  7. class B : virtual A
  8. {
  9. };

那么这样的B能不能被继承呢?假设有个类继承了B,如下

  1. class A
  2. {
  3. private:
  4. A(){}
  5. friend class B;
  6. };
  7. class B : virtual A
  8. {
  9. };
  10. class C : B
  11. {
  12. };

考虑到虚继承的特性,C也将调用A的构造函数构造出一个A,但!!C并不是A的友元类,所以根本不能执行A私有的构造函数,这段程序,如果不实例化C,编译器不会报错,但一旦实例化C,则将报错。

而B是可以正常实例化的一个类,这样就完美实现了一个不能被继承的类:B

2,C++构造函数初始化列表的黑科技

相比于构造函数的各种trick,C++的初始化列表就显得很容易了,只有那么一点要注意:

C++的初始化列表的赋值顺序,是与C++类里面成员变量的声明顺序相关,与初始化列表里的顺序无关

举个例子,以下就会出现莫名错误:

  1. class A
  2. {
  3. public:
  4. A(int _x, int _y):y(_y), x(y){}
  5. public:
  6. int x;
  7. int y;
  8. };

根据声明顺序,在初始化列表中,是先完成x(y)这个步骤,但此时y并没有被赋值,所以得到的x是个随机的值。

3,C++虚函数的黑科技

C++虚函数的问题,几乎是面试必问,实际上需要了解的东西也挺多,我自己在前几次面试,都有些理解有误的地方,或者理解不够完善

这里总结几点吧(以下类都是针对有虚函数的类):

  • 1,每个类都有虚函数表,这个虚函数表是在编译阶段构建,在代码段产生一个vtbl
  • 2,每次实例化的时候,构造函数在前几个字节,产生一个指向虚函数表的指针,指向代码段的那个虚函数表
  • 3,虚函数的实现与调整,是通过移动或变换虚函数表的指针来实现的。
  • 4,纯虚函数是指只声明,但未被实现的虚函数,具有纯虚函数的类不能被实例化,为抽象类

4,C++拷贝构造函数的黑科技

C++的拷贝构造函数是C++默认的四个函数之一:构造函数、析构函数、赋值函数、拷贝构造函数

拷贝构造函数是一种特别的构造函数,在《深度探索C++对象模型》书中说,有三种情况,会导致拷贝构造函数被触发:

  • 1,以一个object的内容作为另一个class object的初始值

    1. class X {...}
    2. X x;
    3. X xx=x;
  • 2,当object被当作参数传递给某个函数时

    1. void foo(X x);
    2. X xx;
    3. foo(xx);
  • 3,函数传回一个class object的时候

    1. X foo_bar()
    2. {
    3. X xx;
    4. // ...
    5. return xx;
    6. }

一般情况下,如果没有提供explicit copy constructor时,会发生什么呢?

一个良好的编译器可以为大部分class objects产生bitwise copies,因为它们有bitwise semantics...

这里说的很神奇,好像我们不需要自己写copy constructor也没问题一样,实际上,bitwise copies在有些情况下是非常不推崇的

首先解释下什么是bitwise copies:这是指,在拷贝过来的时候,把class的内存直接位拷贝过来,即可以看成是内存拷贝(对应的有值拷贝)

位拷贝有很多问题,典型的一个,如果class里面含有分配内存的指针,那么它会将指针指向的地址直接拷贝过来:

  1. class A
  2. {
  3. public:
  4. int *p;
  5. };
  6. int main()
  7. {
  8. A a1;
  9. a1.p=new int[10];
  10. A a2=a1;
  11. cout << a1.p << endl;
  12. cout << a2.p << endl;
  13. return 0;
  14. }

这里可以发现,a1.p的地址与a2.p的地址是一样的,那么,我分配的内存,该由哪个释放呢?我释放了,另一个怎么办呢?

实际上,这种拷贝方式在STL的string里面肯定是要重写的,不能用位拷贝。

《深度探索C++对象模型》中,说class不展现出“bitwise copy semantics”有四种情况:

  • 1,当class含有member object并且后者有一个copy constructor(声明或合成)
  • 2,当class继承一个base class 而后者存在一个copy constructor的时候
  • 3,当class声明了一个或多个virtual functions时
  • 4,当class派生自一个继承串链,其中有一个或多个virtual base classes时

其实主要都是担心,指针在bitwise semantics下,随便复制可能会导致不可预料的错误

在这里说一下赋值函数拷贝构造函数在触发上的区别:

当一个object从无到有时,触发的一定是拷贝构造函数,赋值函数只会在已有的object赋值时,才会触发

5,C++虚继承的黑科技

针对虚继承,可以坦承的一点就是

所有简单的东西,遇到虚继承,似乎都要单独拿出来讨论

C++的黑科技(深入探索C++对象模型)的更多相关文章

  1. 探索前端黑科技——通过 png 图的 rgba 值缓存数据

    本文系原创,欢迎转载,转载请注明作者信息项目地址:SphinxJS在线体验地址:https://jrainlau.github.io/sp... 说起前端缓存,大部分人想到的无非是几个常规的方案,比如 ...

  2. C++的黑科技

    周二面了腾讯,之前只投了TST内推,貌似就是TST面试了 其中有一个问题,"如何产生一个不能被继承的类",这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一 ...

  3. 微软AI发布会,集齐六大亮点召唤黑科技!

    7月12日,微软合作伙伴大会Inspire在华盛顿特区如火如荼地举行.同一天,在相隔5个时区的伦敦,微软还举办了一场关于人工智能的发布会.这是一场智能技术和情感体验两相交融的科技盛宴,既有黑科技,也有 ...

  4. MTSC2019-腾讯WeTest独家揭秘移动游戏测试和质量保障 QA 黑科技

    WeTest 导读 TesterHome 联合腾讯 WeTest 出品 MTSC2019 重磅游戏测试 Topic ,首次公开揭秘腾讯亿级用户游戏背后的质量保障 QA 黑科技. 2019 年,中国游戏 ...

  5. 读书笔记《深度探索c++对象模型》 概述

    <深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...

  6. ACM: FZU 2105 Digits Count - 位运算的线段树【黑科技福利】

     FZU 2105  Digits Count Time Limit:10000MS     Memory Limit:262144KB     64bit IO Format:%I64d & ...

  7. 黑科技项目:英雄无敌III Mod <<Fallen Angel>>介绍

    英雄无敌三简介(Heroes of Might and Magic III) 英3是1999年由New World Computing在Windows平台上开发的回合制策略魔幻游戏,其出版商是3DO. ...

  8. [自己动手玩黑科技] 1、小黑科技——如何将普通的家电改造成可以与手机App联动的“智能硬件”

    NOW, 步 将此黑科技传授予你~ 一.普通家电控制电路板分析 普通家电,其人机接口一般由按键和指示灯组成(高端的会稍微复杂,这里不考虑) 这样交互过程,其实就是:由当前指示灯信息,按照操作流程按相应 ...

  9. 迪士尼黑科技:爬墙机器人 VertiGo

    12 月 30 日,迪士尼研发出的一款爬墙机器人曝光了一段有趣的视频.从视频里可看出这个机器人碰到墙壁时迅速地作出反应爬了上去. 据了解,这个爬墙机器人名叫 VertiGo,由迪士尼研究中心和苏黎世联 ...

随机推荐

  1. LINUX内核设计与实现第三周读书笔记

    LINUX内核设计与实现第三周读书笔记 第一章 LINUX内核简介 1.1 Unix的历史 1969年的夏天,贝尔实验室的程序员们在一台PDR-7型机上实现了Unix这个全新的操作系统. 1973年, ...

  2. hihocoder[Offer收割]编程练习赛19 D 相交的铁路线(树上路径交)

    傻逼题... 裸的树上路径交 两条树上的路径$[a,b]$和$[c,d]$有交,则有$lca(a,b)$在$[c,d]$上或$lca(c,d)$在$[a,b]$上. 其实只要深度大的$lca$在另一条 ...

  3. bzoj2396: 神奇的矩阵(矩阵乘法+随机化)

    这题n三方显然会GG... 运用矩阵乘法的性质A*B*R=A*(B*R)=C*R,于是随机化出一个一列的R,就可以把复杂度降低成n方...大概率是不会错的 #include<iostream&g ...

  4. 【bzoj4872】【shoi2017】分手即是祝愿

    4872: [Shoi2017]分手是祝愿 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 746  Solved: 513[Submit][Statu ...

  5. 用一次FastDFS

    FastDFS c编写的分布式文件系统,用于搭建文件服务器集群提供文件的上传.下载 特点 冗余备份 负载均衡 线性扩容 高性能.高可用 FastDFS架构 Tracker server 负载均衡和调度 ...

  6. HDU 3977 斐波那契循环节

    这类型的题目其实没什么意思..知道怎么做后,就有固定套路了..而且感觉这东西要出的很难的话,有这种方法解常数会比较大吧..所以一般最多套一些比较简单的直接可以暴力求循环节的题目了.. /** @Dat ...

  7. HDU 6158 笛卡尔定理 几何

    LINK 题意:一个大圆中内切两个圆,三个圆两两相切,再不断往上加新的相切圆,问加上的圆的面积和.具体切法看图 思路:笛卡尔定理: 若平面上四个半径为r1.r2.r3.r4的圆两两相切于不同点,则其半 ...

  8. Css Sprite 图片等比缩放图片大小

    图片大小80*40,即每张图片大小40*40,如何以20*20显示图片?1. 首先看下如何以40*40显示第二张图片: 正常显示css代码 .sprite { background-image: ur ...

  9. 51nod1110 距离之和最小 V3

    基准时间限制:1 秒 空间限制:131072 KB 分值: 40  X轴上有N个点,每个点除了包括一个位置数据X[i],还包括一个权值W[i].该点到其他点的带权距离 = 实际距离 * 权值.求X轴上 ...

  10. 51nod 1074 约瑟夫环 V2

    N个人坐成一个圆环(编号为1 - N),从第1个人开始报数,数到K的人出列,后面的人重新从1开始报数.问最后剩下的人的编号. 例如:N = 3,K = 2.2号先出列,然后是1号,最后剩下的是3号. ...